osrlib.core.items
Equipment, inventories, magic items, identification, and curses.
This module takes the two item catalogs that
load_equipment and
load_magic_items return, and turns them into what a
character carries. Start at Inventory, the container
for one character's items, coins, and equipped slots. Fill it with
purchase and
equip. Read it back with
movement_rate_feet for the exploration
movement rate and equipped_item_modifiers
for the stat bonuses worn magic items grant. Every character built by
create_character owns one inventory, and
combat resolution in osrlib.core.combat reads the templates
here to score an attack.
Under a running game you drive all of this through commands
(PurchaseEquipment,
EquipItem,
UnequipItem), which validate, emit events, and
record the change in the save. Call the functions here directly when you are using the
rules without a session.
The catalogs are frozen and shared. Play never mutates a template. It spawns an owned
instance from one instead: ItemInstance for mundane
equipment, MagicItemInstance for magic items.
A magic item instance starts unidentified, and even once identified may still hide a
curse: a revealed cursed item sticks to its bearer until remove curse.
validate_equip and
validate_unequip enforce what a class may wear
or wield: armour and weapon policies, the two-ring cap, and the conflict between a
two-handed weapon and a shield. Both return structured rejections rather than raising.
Torch, holy water, and burning oil appear on both the SRD's weapon table and its gear
list. osrlib reads each as one physical item, not two: they compile as gear with an
embedded combat facet (CombatFacet), the weapons
list contains the pure weapons, and no item has two ids. Class weapon policies govern the
weapons list only, so a cleric may use holy water and a magic-user may throw oil or
swing a torch, as a documented adaptation (see the adaptations
register).
All weights are in coins, the SRD's unit of encumbrance at ten coins to the pound.
Coins themselves weigh 1 each. The maximum load rule always applies, not only under
detailed encumbrance: tracked weight above
MAX_LOAD_COINS means the character cannot move,
under both tracking modes. How much an inventory contains is never capped.
Typical usage:
from osrlib.core.items import Inventory, equip, movement_rate_feet, purchase
from osrlib.core.ruleset import Ruleset
from osrlib.data import load_classes, load_equipment
catalog = load_equipment()
fighter = load_classes().get("fighter")
inventory = Inventory()
inventory.purse.gp = 100
plate = purchase(inventory, catalog.get("plate_mail"))
sword = purchase(inventory, catalog.get("sword"))
torches = purchase(inventory, catalog.get("torch"))
equip(inventory, fighter, plate)
equip(inventory, fighter, sword)
print(torches.quantity, inventory.purse.gp, movement_rate_feet(inventory, Ruleset()))
# 6 29 60
AnyInstance
module-attribute
AnyInstance = Annotated[ItemInstance | MagicItemInstance, Field(discriminator='instance_type')]
Any owned item, mundane or magic, told apart by its instance_type field.
This is what an Inventory contains: its item list and its
equipped slots take either kind, because a character wields a sword and a sword +1 the
same way. Because the union is discriminated, pydantic reads a saved inventory back as
the right classes, and a match on instance_type covers both cases.
The members are ItemInstance and
MagicItemInstance. Only a magic instance has an
id of its own. A mundane one is identified by its template.
BASE_MOVEMENT_FEET
module-attribute
The unencumbered exploration movement rate in feet per turn, printed as 120' (40').
This is what movement_rate_feet returns for a
character carrying nothing that counts, and always what it returns when the ruleset
tracks no encumbrance at all. The parenthesized 40' is the encounter rate, a third of
the base. encounter_movement_rate
computes it.
COIN_VALUES_CP
module-attribute
What one coin of each denomination is worth in copper pieces.
The keys are the denomination names the purse and the treasure tables use (pp, gp,
ep, sp, cp). CoinPurse and
Coins convert with it, and
CoinPurse.spend pays and makes change in these
values. Copper is the exact unit for all coin arithmetic, so that mixed purses convert
without rounding. Gold is the unit of the experience award, at 1 gp to 1 XP.
ItemTemplate
module-attribute
ItemTemplate = Annotated[
WeaponTemplate | ArmourTemplate | GearTemplate | AmmunitionTemplate, Field(discriminator="item_type")
]
Any one of the four mundane equipment templates, told apart by its item_type field.
Annotate a parameter or a field with this when it takes equipment of any kind:
purchase and
validate_purchase do, and so does the items
bundle of Adventure. Because the union is
discriminated, pydantic reads a serialized item back as the right class without
guessing, and a match on item_type covers every case.
The members are WeaponTemplate,
ArmourTemplate,
GearTemplate, and
AmmunitionTemplate. Only weapons, armour, and
ammunition have a weight. Only gear and ammunition have a lot size.
MAX_LOAD_COINS
module-attribute
The most a character can carry in coins of weight before movement drops to 0.
Ten coins weigh a pound, so this is 160 pounds. Every weight this module reports is in
this unit. movement_rate_feet returns 0 above
this figure under both tracking modes, basic and detailed, and the detailed mode's
slowest band ends here. Nothing in the library stops you from putting more in an
Inventory, but the load then shows up as a movement rate
of 0.
MAX_RINGS_WORN
module-attribute
How many magic rings a character can wear at once: one on each hand.
validate_equip rejects a third ring with
items.ring.hands_full rather than letting it on, because in the tabletop rules a
third ring makes none of them function. Nothing reads this constant at attack or
effect time, so changing it here would let a third ring be worn without granting it
any behavior. Apply your own cap before you call equip if
you want more ring slots.
MISC_GEAR_WEIGHT_COINS
module-attribute
The flat weight in coins that any amount of miscellaneous gear adds under detailed encumbrance.
The SRD prices weapons and armour individually but gives adventuring gear no per-item
weights, so equipment_weight_coins adds
this figure once when a character carries any gear at all, and nothing more however
much gear that is.
AmmunitionTemplate
Bases: BaseModel
Ammunition for a missile weapon: arrows, quarrels, sling stones.
Bought like gear, in lots: one purchase at the listed price delivers lot_size
units. Ammunition never weighs anything, because the SRD folds the weight of the
ammunition and its container into the missile weapon's own listed weight and gives the
ammunition table no weight column. It is not equippable either: wield the bow, and the
arrows go in the item list. Sling stones are free, which compiles to a cost of 0.
item_type
class-attribute
instance-attribute
item_type: Literal['ammunition'] = 'ammunition'
Always "ammunition".
name
instance-attribute
name: str
The display name as the price list prints it, which names the lot: "Arrows (quiver of 20)".
cost_gp
class-attribute
instance-attribute
The listed price in gold pieces, for one lot. 0 for sling stones.
lot_size
class-attribute
instance-attribute
How many units one purchase delivers, for example 20 arrows.
weight_coins
class-attribute
instance-attribute
Always 0.
material
class-attribute
instance-attribute
What the ammunition is made of, for the silver immunity exemption. See Material.
ArmourCategory
Bases: StrEnum
How bulky a suit of body armour is, for the basic encumbrance rates.
Basic encumbrance sets a character's movement rate from what they wear rather than from
what they weigh, and this is the column it looks up:
movement_rate_feet reads the category of the
armour in the worn slot. Wearing nothing is the absence of a category, not a value here,
so an unarmoured character has no ArmourCategory at all. Enchanted armour moves like
the mundane armour it is made from, since enchantment lightens a suit without making it
less bulky.
The wire values are "light" and "heavy", serialized into the compiled equipment
data. Changing them is a schema_version bump.
ArmourTemplate
Bases: BaseModel
A suit of body armour or the shield, from the SRD's armour table.
Body armour sets a wearer's armour class outright and a shield adds a bonus to it, so
one of the two field groups is filled and the other is empty: body armour has ac,
ac_ascending, and category, while the shield has ac_bonus alone. Ask
is_shield which kind you have rather
than testing the fields. Get one from
EquipmentCatalog.get, buy it with
purchase, and put it on with
equip, which routes body armour to the worn slot and the
shield to the shield slot.
Both armour class formats are here because the tabletop rules print both: the descending scale, where lower is better and unarmoured is 9, and the ascending scale in brackets, where higher is better and unarmoured is 10. Which one a game shows its players is the game's choice. The rules resolve identically either way.
item_type
class-attribute
instance-attribute
item_type: Literal['armour'] = 'armour'
Always "armour".
cost_gp
class-attribute
instance-attribute
The listed price in gold pieces.
weight_coins
class-attribute
instance-attribute
The weight in coins. Enchanted armour weighs half this.
ac
class-attribute
instance-attribute
ac: int | None = None
Body armour's armour class on the descending scale. None on the shield.
ac_ascending
class-attribute
instance-attribute
ac_ascending: int | None = None
The same protection on the ascending scale. None on the shield.
ac_bonus
class-attribute
instance-attribute
ac_bonus: int | None = None
The shield's bonus, which improves the wearer's armour class by 1 on either scale. None on body armour.
category
class-attribute
instance-attribute
category: ArmourCategory | None = None
How bulky the suit is, for the basic encumbrance movement rates. See
ArmourCategory. None on the shield.
overrides_applied
class-attribute
instance-attribute
Field paths a compiler override corrected when this row was compiled from the SRD.
is_shield
property
is_shield: bool
Whether this row is the shield rather than a suit of body armour.
Read it instead of testing the armour class fields yourself: the shield has an
armour class bonus and body armour has base values, and
equip sends the two to different slots.
Returns:
| Type | Description |
|---|---|
bool
|
True for the shield, False for body armour. |
ArmourTypeRow
Bases: BaseModel
One band of the Magic Armour Type table: the d8 rolls that settle what a generated suit is made of.
roll_min
class-attribute
instance-attribute
The lowest d8 result in this band.
roll_max
class-attribute
instance-attribute
The highest d8 result in this band.
base_item_id
instance-attribute
base_item_id: str
The mundane armour the band yields, for example "chainmail". See the equipment id index.
CoinPurse
Bases: BaseModel
The coins a character is carrying, by denomination.
Every Inventory has one, and it is mutable: this is the
money that gets spent. Ask can_afford before
you charge, and spend to charge. Both work in
whole gold pieces, the unit the equipment lists price in.
Paying takes the smallest coins first and returns change in the largest, so a purse always ends up with the fewest coins that preserve its value. That matters because coins are weight: each coin weighs 1 whatever its metal, and a purse full of copper slows a character down.
pp
class-attribute
instance-attribute
Platinum pieces, worth 5 gp each.
ep
class-attribute
instance-attribute
Electrum pieces, worth half a gold piece each.
sp
class-attribute
instance-attribute
Silver pieces, ten to the gold piece.
cp
class-attribute
instance-attribute
Copper pieces, a hundred to the gold piece.
value_cp
property
value_cp: int
What the purse is worth in copper pieces.
Copper is the exact unit: totalling a mixed purse in gold would lose the odd silver and copper to rounding.
Returns:
| Type | Description |
|---|---|
int
|
The value in copper pieces. |
total_coins
property
total_coins: int
How many coins are in the purse, which is also its weight in coins.
Every coin weighs 1 whatever its metal, so this figure goes straight into
treasure_weight_coins.
Returns:
| Type | Description |
|---|---|
int
|
The number of coins. |
can_afford
Return whether the purse can cover a price in gold pieces.
Ask before you charge: spend raises rather than
going into debt. Coins of every denomination count, so a purse with no gold at all can
still afford a gold-priced item.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cost_gp
|
int
|
The price in whole gold pieces. Not negative. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the purse is worth at least that much. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
spend
spend(cost_gp: int) -> None
Pay a price in gold pieces out of the purse, making change.
Mutates the purse. Coins go out smallest denomination first, and any overpayment comes back as the fewest coins that make up the difference, largest denomination first: paying 1 gp from a purse of two gold and five silver spends the silver, then a gold piece to cover the rest, and returns the change as a single electrum piece. The result is deterministic and preserves value exactly, so a purse can be spent from and saved without drifting.
purchase calls this for you when a character buys
equipment. Call it directly for anything else a game charges for, like lodging or
travel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cost_gp
|
int
|
The price in whole gold pieces. Not negative. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
Coins
Bases: BaseModel
A fixed pile of coins: generated treasure, the contents of a chest, a pile dropped on the floor.
Frozen, unlike the CoinPurse a character carries. This
is what generate_treasure reports and what a
dungeon feature contains until someone picks it up. Adding it to a character means adding
each denomination to their purse.
pp
class-attribute
instance-attribute
Platinum pieces, worth 5 gp each.
ep
class-attribute
instance-attribute
Electrum pieces, worth half a gold piece each.
sp
class-attribute
instance-attribute
Silver pieces, ten to the gold piece.
cp
class-attribute
instance-attribute
Copper pieces, a hundred to the gold piece.
total_coins
property
total_coins: int
How many coins are in the pile, whatever they are worth.
This is also its weight in coins, since every coin weighs 1 whatever its metal.
Returns:
| Type | Description |
|---|---|
int
|
The number of coins. |
CombatFacet
Bases: BaseModel
The combat statistics of a piece of gear that can also be used as a weapon.
Torch, holy water, and burning oil are printed on both the SRD's weapon table and its
gear list. osrlib compiles each as one gear item whose combat field contains this facet,
so the item has a single id and a single weight. Attack resolution reads the facet
exactly as it reads a WeaponTemplate, and class
weapon policies do not apply to it: a cleric may throw holy water and a magic-user may
swing a torch. That exemption is a documented adaptation (see the adaptations
register).
qualities
instance-attribute
qualities: tuple[WeaponQuality, ...]
What the item can do when used as a weapon. See WeaponQuality. Holy water
and burning oil have the splash quality, which is what makes them burn on for a second round.
missile_ranges
class-attribute
instance-attribute
missile_ranges: MissileRanges | None = None
The three range bands, present exactly when qualities includes the missile quality.
EquipmentCatalog
Bases: BaseModel
The whole mundane equipment list: what a shop sells and what a character can own.
Call load_equipment to get the shipped catalog. It is
frozen, cached, and shared, so hold onto the one you are given rather than loading it
per lookup. Reach an item by id with
get, or iterate a list when you are
building a shop screen. An Adventure that bundles
item templates of its own is given a catalog with those added.
Ids are unique across the four equipment lists, and across the magic item catalog too, so an id names exactly one thing anywhere in the library.
armour
instance-attribute
armour: tuple[ArmourTemplate, ...]
Every suit of body armour, plus the shield.
treasure_weights
instance-attribute
treasure_weights: tuple[TreasureWeight, ...]
What each kind of treasure weighs. See TreasureWeight.
get
get(item_id: str) -> WeaponTemplate | ArmourTemplate | GearTemplate | AmmunitionTemplate
Return the template with item_id, whichever of the four lists contains it.
Use this whenever you have an id and need the item: before
purchase, when rendering what a character carries, or
when resolving an id an adventure supplied. Lookup is a scan, so hoist it out of a hot
loop if you are resolving many ids at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item_id
|
str
|
Any equipment id this catalog contains. For the shipped catalog
( |
required |
Returns:
| Type | Description |
|---|---|
WeaponTemplate | ArmourTemplate | GearTemplate | AmmunitionTemplate
|
The template. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no item has that id. The message names the id. |
Examples:
GearTemplate
Bases: BaseModel
A piece of adventuring gear: a torch, a rope, a backpack, a flask of oil.
Gear is what everything that is neither weapon, armour, nor ammunition compiles to. Get
one from EquipmentCatalog.get and buy it
with purchase. Gear sells in lots: one purchase at the
listed price delivers lot_size units, so buying torches once costs 1 gp and yields
six torches in one ItemInstance.
Most gear cannot be equipped. The three items with a combat facet can be, and are the
only gear equip accepts. Gear has no per-item weight in the
SRD, so detailed encumbrance charges a flat
MISC_GEAR_WEIGHT_COINS once for carrying
any of it.
name
instance-attribute
name: str
The display name as the price list prints it, which names the lot size for gear sold in lots:
"Torches (6)", "Iron spikes (12)".
cost_gp
class-attribute
instance-attribute
The listed price in gold pieces, for one lot.
lot_size
class-attribute
instance-attribute
How many units one purchase at cost_gp delivers. 1 for gear sold singly.
capacity_coins
class-attribute
instance-attribute
capacity_coins: int | None = None
How much fits in the container, in coins of weight, where the SRD gives a figure (backpack, small sack, large
sack). None for gear that contains nothing. Nothing in the library enforces the figure, so enforce container
limits in your own game if you want them.
combat
class-attribute
instance-attribute
combat: CombatFacet | None = None
The combat statistics for the three items that are also weapons. See
CombatFacet. None for everything else.
params
class-attribute
instance-attribute
The exploration mechanics the SRD's gear table prints, keyed by name: a torch's burn_turns and
light_radius_feet, the tinder box's light_chance_in_six, and so on. The dungeon-crawl procedures in
osrlib.crawl.exploration read these.
GeneratedTreasure
Bases: BaseModel
Everything one roll of a treasure table produced.
What every generation entry point in
osrlib.core.treasure returns. Nothing is placed or given to
anyone: put the coins in a purse, the valuables and magic items in an
Inventory, or keep the whole thing in a dungeon feature
until the party opens it. Any of the three fields can be empty, and an unlucky roll
leaves all three empty.
coins
class-attribute
instance-attribute
The coins, by denomination. See Coins.
valuables
class-attribute
instance-attribute
valuables: tuple[ValuableInstance, ...] = ()
The gems and jewellery. See ValuableInstance.
magic_items
class-attribute
instance-attribute
magic_items: tuple[MagicItemInstance, ...] = ()
The magic items, already rolled up as instances. See MagicItemInstance.
Inventory
Bases: BaseModel
Everything one character carries: items, coins, valuables, and what is in hand or worn.
Every Character owns one. Build it up with
purchase and equip, search
it with carried_item and
magic_item, and weigh it with
tracked_weight_coins or
movement_rate_feet. Under a running game the
commands do all of this and record it in the save.
An instance lives in exactly one place: equipping moves it out of the item list and into
its slot, and unequipping moves it back. So iterate
all_instances rather than items when
you want everything a character has. The item list keeps the order things were added,
which is what makes a saved game replay identically.
Nothing here caps what a character can carry. Weight is not a limit but a movement rate:
past MAX_LOAD_COINS the character cannot move.
items
class-attribute
instance-attribute
items: list[AnyInstance] = []
What is carried but not in use, in the order it was acquired. See AnyInstance.
valuables
class-attribute
instance-attribute
valuables: list[ValuableInstance] = []
Carried gems and jewellery. See ValuableInstance.
worn_armour
class-attribute
instance-attribute
worn_armour: AnyInstance | None = None
The suit of body armour being worn, or None.
shield
class-attribute
instance-attribute
shield: AnyInstance | None = None
The shield being carried, or None.
wielded
class-attribute
instance-attribute
wielded: list[AnyInstance] = []
What is in hand: weapons, a lit torch, a wand. A two-handed weapon here rules out a shield.
rings
class-attribute
instance-attribute
rings: list[MagicItemInstance] = []
The worn rings, at most MAX_RINGS_WORN.
all_instances
all_instances() -> list[ItemInstance | MagicItemInstance]
Return every instance the character has, carried or equipped.
Use it whenever "what does this character have" is the question: weighing a load,
looking for an item, rendering a character sheet. Equipped items are not in items, so
reading that field alone misses the sword in hand.
Returns:
| Type | Description |
|---|---|
list[ItemInstance | MagicItemInstance]
|
A new list: the item list first, then worn armour, the shield, what is wielded, |
list[ItemInstance | MagicItemInstance]
|
and the rings. The order is stable, so anything that iterates it behaves the same |
list[ItemInstance | MagicItemInstance]
|
on replay. |
equipped_instances
equipped_instances() -> list[ItemInstance | MagicItemInstance]
Return only what the character has in use.
This is the set that grants bonuses:
equipped_item_modifiers scans exactly
this. For everything a character has, carried items included, call
all_instances.
Returns:
| Type | Description |
|---|---|
list[ItemInstance | MagicItemInstance]
|
A new list: worn armour, the shield, what is wielded, then the rings, in that |
list[ItemInstance | MagicItemInstance]
|
order. |
magic_item
magic_item(instance_id: str) -> MagicItemInstance | None
Return the carried magic item with instance_id, or None.
Magic items are addressed by their own ids, which is how a command names the one to drink, read, or take off. It looks everywhere the character keeps things: pack, hands, worn slots, rings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance_id
|
str
|
The instance id, for example |
required |
Returns:
| Type | Description |
|---|---|
MagicItemInstance | None
|
The instance, or |
carried_item
carried_item(item_id: str) -> ItemInstance | MagicItemInstance | None
Return the first instance of a catalog id the character is carrying, mundane or magic, or None.
Ask this when you know what you want but not which copy: does anyone have a torch, does
this character still have arrows. It looks everywhere, in
all_instances order: pack, hands, worn
slots, rings. A mundane instance matches on its template's id and a magic one on its
template_id, and since equipment ids and magic item ids never collide, an id can
resolve to only one of the two.
A spent stack never matches. A quantity of zero is expressible only on a magic instance, like an emptied quiver of arrows +1, and it means the character no longer has any, so whatever this returns you can take a unit from.
Valuables never match either: a gem has no catalog id, only an id of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item_id
|
str
|
The catalog id to look for: an equipment id (see the equipment id index, or an id an adventure bundles) or a magic item id (see the magic item id index). |
required |
Returns:
| Type | Description |
|---|---|
ItemInstance | MagicItemInstance | None
|
The instance, or |
Examples:
from osrlib.core.items import Inventory, ItemInstance
from osrlib.data import load_equipment
inventory = Inventory(items=[ItemInstance(template=load_equipment().get("torch"), quantity=6)])
carried = inventory.carried_item("torch")
assert carried is not None and carried.quantity == 6
assert inventory.carried_item("lantern") is None
ItemInstance
Bases: BaseModel
A stack of mundane items a character owns.
Made by purchase, or constructed directly when you are
giving a character something without charging for it, and carried in an
Inventory. Unlike a magic item, a mundane instance has
no id of its own: it is identified by its template, so two stacks of the same item are
interchangeable.
instance_type
class-attribute
instance-attribute
instance_type: Literal['item'] = 'item'
Always "item".
template
instance-attribute
template: ItemTemplate
The item itself, one of the four equipment templates. See ItemTemplate. The
whole template is embedded rather than referenced by id, so an instance of an item an adventure bundled stays
readable without that adventure.
MagicArmourTypeTable
Bases: BaseModel
The Magic Armour Type table: what a generated suit of Armour +N turns out to be made of.
The generation tables produce enchanted armour without saying which armour, so
instantiate_magic_item rolls this d8 to
settle it and records the answer on the instance's base_item_id. Roll it yourself with
base_for_roll only when you
are placing armour by hand and want the same distribution.
rows
class-attribute
instance-attribute
rows: tuple[ArmourTypeRow, ...] = Field(min_length=1)
The bands, in order, covering the whole d8. See ArmourTypeRow.
base_for_roll
Return the mundane armour a d8 roll makes a generated suit out of.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
roll
|
int
|
The d8 result, 1 to 8. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The mundane armour id, one of |
str
|
shipped table. Look it up with |
str
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the roll is outside 1 to 8. |
Examples:
MagicItemCatalog
Bases: BaseModel
The whole magic item list, with the tables that generate from it.
Call load_magic_items to get the shipped catalog. It
is frozen, cached, and shared. Reach an item by id with
get, and a type's generation table with
sub_table. Treasure generation in
osrlib.core.treasure loads this catalog itself, so you need it
only to read items, not to generate them.
Magic item ids never collide with equipment ids, so a single id names one thing across both catalogs. An adventure cannot bundle magic items of its own. It places the shipped ones.
sub_tables
instance-attribute
sub_tables: tuple[MagicSubTable, ...]
One generation table per master-table type. See MagicSubTable.
armour_type
instance-attribute
armour_type: MagicArmourTypeTable
What a generated suit of enchanted armour is made of. See
MagicArmourTypeTable.
scroll_spell_levels
instance-attribute
scroll_spell_levels: ScrollSpellLevelTable
Which spell level each spell on a generated scroll is. See
ScrollSpellLevelTable.
sentient_swords
instance-attribute
sentient_swords: SentientSwordTables
The tables for rolling up a sentient sword. See SentientSwordTables.
get
get(item_id: str) -> MagicItemTemplate
Return the magic item template with item_id.
Use it to turn an id into an item: the template_id on a
MagicItemInstance (for which
magic_item_template is the shorthand), an id
an adventure places, or an id you are generating from. Lookup is a scan, so hoist it out
of a hot loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item_id
|
str
|
A magic item id from
|
required |
Returns:
| Type | Description |
|---|---|
MagicItemTemplate
|
The template. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no item has that id. |
Examples:
sub_table
sub_table(category: MagicItemType) -> MagicSubTable
Return the generation table for one type of the master Magic Item Type table.
Call it when you are rolling a type's table yourself. When you want a whole item rolled,
call generate_magic_item instead, which
rolls the master table, this one, and the item's own details.
The master table's rod, staff, and wand row covers three catalog categories, and asking
for ROD_STAFF_WAND returns the one table that produces all three.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
category
|
MagicItemType
|
The master-table type. See
|
required |
Returns:
| Type | Description |
|---|---|
MagicSubTable
|
The sub-table. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no sub-table covers that type. |
MagicItemCategory
Bases: StrEnum
What kind of magic item a template is, in the magic item catalog.
Every MagicItemTemplate has one. The category
governs how the item is handled: what equip does with it,
whether treasure_weight_coins weighs it as
treasure, and whether generation rolls sentience for it.
These are the categories of the catalog, not the types of the random-generation table.
The table's rod, staff, and wand row covers three categories here. See
MagicItemType for the table's own types and
MagicItemCatalog.sub_table for how one
maps to the other.
The wire values are the lowercase names below, serialized into the compiled magic item
data and into saves. Changing one is a schema_version bump.
ARMOUR
class-attribute
instance-attribute
Enchanted armour and shields. Worn in the armour or shield slot.
MISC
class-attribute
instance-attribute
Everything with no other home: cloaks, boots, bags, crystal balls.
ROD
class-attribute
instance-attribute
Rods. Wielded, and charged at creation like staves and wands.
STAFF
class-attribute
instance-attribute
Staves. Wielded, and charged at creation like rods and wands.
WAND
class-attribute
instance-attribute
Wands. Wielded, charged at creation, and restricted to arcane casters.
SCROLL
class-attribute
instance-attribute
Scrolls and treasure maps. Not equippable.
SWORD
class-attribute
instance-attribute
Enchanted swords, the only items that can be sentient.
MagicItemEffect
Bases: BaseModel
The part of a magic item's behavior the engine resolves for you.
An item whose page describes something the engine can execute has one of these. The rest
have their page text in the template's manual field, for a game to narrate and
adjudicate itself. kind names which behavior runs, and the behavior reads the fields
it needs, so most fields are empty on most items.
Read kind to decide what an item does. Read modifiers to show what a worn item
grants, since
equipped_item_modifiers returns exactly
those for every equipped always-active item.
The behaviors that ship are worn_modifiers, potion, damage_area, condition_area,
healing, save_or_die, on_hit_drain, striking, ward, regeneration, and
light.
kind
class-attribute
instance-attribute
Which behavior executes the item.
modifiers
class-attribute
instance-attribute
modifiers: tuple[ModifierSpec, ...] = ()
Stat modifiers the item grants. See ModifierSpec.
condition
class-attribute
instance-attribute
condition: str | None = None
The condition the item inflicts, for the behaviors that inflict one.
damage_dice
class-attribute
instance-attribute
damage_dice: str | None = None
The damage it deals, as a dice expression.
heal_dice
class-attribute
instance-attribute
heal_dice: str | None = None
The hit points it restores, as a dice expression.
element
class-attribute
instance-attribute
element: str | None = None
The damage element, for example "fire", for the target's immunity checks.
save_category
class-attribute
instance-attribute
save_category: str | None = None
Which saving throw column the target rolls against.
save_on
class-attribute
instance-attribute
save_on: Literal['negates', 'half'] | None = None
What a successful save does: "negates" the effect entirely, or "half" the damage.
shape
class-attribute
instance-attribute
shape: str | None = None
The area's shape, for an area effect.
dimensions
class-attribute
instance-attribute
The area's measurements in feet, keyed by name.
range_feet
class-attribute
instance-attribute
range_feet: int | None = None
How far the effect reaches.
duration_unit
class-attribute
instance-attribute
duration_unit: str | None = None
The unit the duration counts in, for example "turns" or "rounds".
duration_amount
class-attribute
instance-attribute
duration_amount: int | None = None
A fixed duration, in duration_units.
duration_dice
class-attribute
instance-attribute
duration_dice: str | None = None
A rolled duration, as a dice expression, in duration_units.
MagicItemInstance
Bases: BaseModel
One magic item a character owns, with everything that differs from copy to copy.
Templates are shared and frozen. This is the copy in play, and it is mutable. Treasure
generation makes them
(instantiate_magic_item and
generate_magic_item), an
Inventory contains them, and
magic_item_template gets you back to the
template behind one.
An instance starts unidentified, and a player-facing view shows it as an unknown item
until it is not. Identification happens in play: drinking the potion, swinging the
sword, wearing the ring. A cursed item reveals its curse the same way, and once revealed
it cannot be taken off until remove curse:
unequip rejects with items.curse.stuck.
instance_type
class-attribute
instance-attribute
instance_type: Literal['magic_item'] = 'magic_item'
Always "magic_item". It is what tells this apart from a mundane
ItemInstance when both are stored in one list.
instance_id
instance-attribute
instance_id: str
This copy's own id, for example "magic-item-0003", allocated by
IdAllocator. Commands that act on a magic item name it by this, not by its
template id.
charges_remaining
class-attribute
instance-attribute
charges_remaining: int | None = None
How many charges are left in a rod, staff, or wand, or None for an item that has no charges. Keep this out of
the player's view: in the tabletop rules a charge count cannot be discovered.
quantity
class-attribute
instance-attribute
How many the stack contains, for enchanted ammunition. A stack at 0 is spent and no longer counts as carried.
identified
class-attribute
instance-attribute
identified: bool = False
True once the party knows what the item is. A player-facing view masks an unidentified item's name and properties.
cursed_revealed
class-attribute
instance-attribute
cursed_revealed: bool = False
True once the curse has shown itself. From then on the item cannot be unequipped or given away until remove curse.
base_item_id
class-attribute
instance-attribute
base_item_id: str | None = None
The mundane item underneath an enchanted weapon, arrow, or suit of armour. See the equipment id index. For generic enchanted armour this is what the Magic Armour Type roll settled on.
sentience
class-attribute
instance-attribute
sentience: SwordSentience | None = None
The sword's mind, for a sentient sword. See SwordSentience. None on
everything else.
state
class-attribute
instance-attribute
What this copy records, keyed by name: the effects a worn item has attached, an energy-drain sword's remaining drains, the day a staff of healing last healed each target, the spells left on a scroll.
MagicItemTemplate
Bases: BaseModel
A magic item, compiled from the generation tables and the per-item pages.
Get one from MagicItemCatalog.get, or from
magic_item_template when what you have is an
instance. Templates are frozen and shared, and play uses
MagicItemInstances spawned from them by
instantiate_magic_item, which is what
rolls the details that differ from copy to copy.
A cursed item's penalty is a negative bonus, so the arithmetic is the same as for a good
item. The two cursed armours that fix armour class outright use ac_set instead.
id
instance-attribute
id: str
The catalog id, for example "potion_of_healing". See the magic item id index.
category
instance-attribute
category: MagicItemCategory
What kind of item it is. See MagicItemCategory.
base_item_id
class-attribute
instance-attribute
base_item_id: str | None = None
The mundane equipment id the enchantment overlays, for an enchanted weapon, arrow, or shield. See the equipment
id index. None for everything else, including generic enchanted armour, whose base is rolled per
instance on the Magic Armour Type table.
attack_bonus
class-attribute
instance-attribute
attack_bonus: int = 0
What the item adds to an attack roll. Negative when cursed.
damage_bonus
class-attribute
instance-attribute
damage_bonus: int = 0
What it adds to damage. Negative when cursed.
ac_bonus
class-attribute
instance-attribute
ac_bonus: int = 0
What it adds to the wearer's armour class. Negative when cursed.
ac_set
class-attribute
instance-attribute
ac_set: int | None = None
The armour class the item forces, on the descending scale, for the cursed suits whose page prints AC 9 [10].
None on every other item.
ac_set_ascending
class-attribute
instance-attribute
ac_set_ascending: int | None = None
The same forced armour class on the ascending scale.
versus
class-attribute
instance-attribute
versus: tuple[VersusBonus, ...] = ()
Bonuses against particular enemies. See VersusBonus.
cursed
class-attribute
instance-attribute
cursed: bool = False
True when the item is cursed. A cursed instance reveals itself in use, and a revealed cursed item cannot be taken off until remove curse.
charges_dice
class-attribute
instance-attribute
charges_dice: str | None = None
How many charges are in a new copy, as a dice expression, for a rod, staff, or wand. None for an item with no
charges.
quantity_dice
class-attribute
instance-attribute
quantity_dice: str | None = None
How many arrive at once, as a dice expression, for enchanted ammunition.
usable_by
class-attribute
instance-attribute
Who can use it. See UsableBy.
always_active
class-attribute
instance-attribute
always_active: bool = False
True when the item works while it is worn or wielded, with nothing to invoke.
effect
class-attribute
instance-attribute
effect: MagicItemEffect | None = None
What the engine resolves for the item. See MagicItemEffect. None for an
item whose behavior is left to the game, which has manual prose instead.
params
class-attribute
instance-attribute
Per-item scalars, keyed by name, for behaviors that read them.
manual
class-attribute
instance-attribute
The item's page text, for the parts a game adjudicates itself. Show these lines to the referee.
weight_coins
class-attribute
instance-attribute
The weight in coins. For an enchanted weapon or suit of armour this is the base item's weight, armour halved. For potions, scrolls, and devices it is the figure the treasure encumbrance rows give.
hoard_recipe
class-attribute
instance-attribute
hoard_recipe: tuple[TreasureEntry, ...] = ()
The treasure a map leads to, as printed treasure entries. See
TreasureEntry. Empty on everything but a treasure map. Generate the hoard
with generate_treasure_entries.
curses
class-attribute
instance-attribute
curses: tuple[ScrollCurse, ...] = ()
The cursed scroll's example curses. See ScrollCurse. Empty on every other
item.
MagicSubTable
Bases: BaseModel
One magic item type's generation table: everything that type can produce, and the rolls that produce it.
There is one of these per type of the master table. Reach the one you want with
MagicItemCatalog.sub_table. Roll on it
with row_for_basic or
row_for_expert, depending on the
tier, or let generate_magic_item do the
whole job: pick the type, roll the sub-table, and instantiate the item.
The rules print two columns, B for Basic play and X for Expert. The B column is a small die whose faces reach only part of the outcome list. The X column is a d% covering all of it.
category
instance-attribute
category: MagicItemType
The master-table type this table generates. See MagicItemType.
basic_die
class-attribute
instance-attribute
How many sides the B column's die has, for example 8 for a d8.
rows
class-attribute
instance-attribute
rows: tuple[MagicSubTableRow, ...] = Field(min_length=1)
The outcomes, in printed order. See MagicSubTableRow.
row_for_basic
row_for_basic(roll: int) -> MagicSubTableRow
Return the row a Basic-tier roll of the table's small die selects.
Call this when you are rolling a table by hand and the game is at the Basic tier. For
the Expert tier call
row_for_expert. Roll the die
yourself, from the treasure stream, so the draw is part of the reproducible sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
roll
|
int
|
The die result, 1 through |
required |
Returns:
| Type | Description |
|---|---|
MagicSubTableRow
|
The selected row. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no row has that face, which includes every roll outside the die's range. |
Examples:
row_for_expert
row_for_expert(roll: int) -> MagicSubTableRow
Return the row an Expert-tier d% roll selects.
The X column covers the whole d%, so every roll from 1 to 100 selects a row. For the
Basic tier call row_for_basic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
roll
|
int
|
The d% result, 1 to 100, with a rolled |
required |
Returns:
| Type | Description |
|---|---|
MagicSubTableRow
|
The selected row. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the roll is outside 1 to 100. |
MagicSubTableRow
Bases: BaseModel
One outcome of a magic item generation sub-table: the item it yields and the rolls that select it.
The rules print two probability columns for every generation table, B for Basic play and X for Expert, and osrlib calls the choice between them the tier. The two columns index the same list of outcomes independently, so a row can sit in the X column without appearing in the B column at all.
You rarely read a row yourself:
generate_magic_item rolls one and hands
the result to
instantiate_magic_item. Read rows when
you are showing a referee what a table can produce.
item_ids
class-attribute
instance-attribute
What the row yields. See the magic item id index. One id usually, two for the armour rows that come with a shield.
basic_value
class-attribute
instance-attribute
basic_value: int | None = None
The single face of the sub-table's small die that selects this row in the B column. None when the printed cell
is blank, which means the B column cannot produce this row.
expert_min
class-attribute
instance-attribute
The lowest d% roll that selects this row in the X column.
expert_max
class-attribute
instance-attribute
The highest, with a printed 00 read as 100. The X bands are contiguous and cover the whole d%.
Material
Bases: StrEnum
What a weapon or piece of ammunition is made of, where the rules care.
Some monsters are hurt only by silver or magical weapons, and this is how a mundane
weapon claims the silver exemption: the damage pipeline in
osrlib.core.combat reads it when it checks a target's
immunities. Everything else is STANDARD, the default on
WeaponTemplate and
AmmunitionTemplate. The shipped catalog uses
SILVER for silver-tipped arrows.
The wire values are "standard" and "silver", serialized into the compiled equipment
data. Changing them is a schema_version bump.
MissileRanges
Bases: BaseModel
A missile weapon's three range bands, near to far.
Attack resolution measures the distance to the target, finds the band it falls in, and
applies that band's modifier: +1 at short range, nothing at medium, −1 at long. Beyond
the long band the shot is out of range. A
WeaponTemplate or
CombatFacet has one of these exactly when it has
the MISSILE quality of
WeaponQuality. The two are validated together at
load.
RangeBand
Bases: BaseModel
One missile range band in feet, as the SRD prints it (5'–80').
Three of these make up a weapon's MissileRanges,
and the band a shot falls into sets its attack modifier. Bands come from the
compiled equipment data (load_equipment). Construct one
only when an adventure bundles a missile weapon of its own. Both bounds are inclusive,
and a shot past the long band's maximum cannot be attempted at all.
min_feet
class-attribute
instance-attribute
The band's nearest distance in feet, inclusive.
ScrollCurse
Bases: BaseModel
One of the cursed scroll's example curses.
The SRD lists six example curses a cursed scroll can have and leaves the choice to the referee, and osrlib compiles them as rows so a game can roll or pick among them. Two are resolved by the engine and the rest are prose for a game to adjudicate.
prose
instance-attribute
prose: str
The curse as its page prints it. Show this to the referee or the player.
wired
class-attribute
instance-attribute
wired: bool = False
True when the engine resolves the curse itself: the energy drain, which takes a level, and the slow healing, which doubles the rest a day's natural healing takes and halves what healing magic restores. False means the prose is all there is, and the game decides what happens.
ScrollSpellLevelRow
Bases: BaseModel
One row of the Random Scroll Spell Level table: the rolls that select a level, and the level they give.
The B column here is bands of a d6 rather than single faces, and its bounds are None
on the rows only the X column can reach.
basic_min
class-attribute
instance-attribute
basic_min: int | None = None
The lowest d6 result in this row's B band, or None when the row is Expert-only.
basic_max
class-attribute
instance-attribute
basic_max: int | None = None
The highest d6 result in the row's B band, or None when the row is Expert-only.
expert_min
class-attribute
instance-attribute
The lowest d% result in this row's X band.
expert_max
class-attribute
instance-attribute
The highest d% result in the row's X band.
arcane_level
class-attribute
instance-attribute
The spell level this row gives a magic-user scroll.
ScrollSpellLevelTable
Bases: BaseModel
The Random Scroll Spell Level table: which spell level each spell on a generated scroll is.
instantiate_magic_item rolls this once
per spell on a scroll, then picks a spell of that level from the scroll's list.
Roll it yourself with
level_for_basic or
level_for_expert when you
are writing a scroll by hand.
rows
class-attribute
instance-attribute
rows: tuple[ScrollSpellLevelRow, ...] = Field(min_length=1)
The rows, in printed order. See ScrollSpellLevelRow.
level_for_basic
Return the spell level a Basic-tier d6 roll gives.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
roll
|
int
|
The d6 result, 1 to 6. |
required |
divine
|
bool
|
True for a cleric scroll, False for a magic-user scroll. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The spell level. Pass it to |
int
|
|
int
|
spells you can choose among. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no band covers the roll. |
Examples:
level_for_expert
Return the spell level an Expert-tier d% roll gives.
The X column reaches levels the B column cannot, which is what makes scrolls found in Expert play stronger.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
roll
|
int
|
The d% result, 1 to 100. |
required |
divine
|
bool
|
True for a cleric scroll, False for a magic-user scroll. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The spell level. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the roll is outside 1 to 100. |
SentientSwordTables
Bases: BaseModel
Every table that goes into rolling up a sentient sword.
Generation runs these in the order the rules print them, and
instantiate_magic_item does it for you
whenever it creates a sword: first the 1-in-20 check for a sword with a special purpose,
which is always sentient at intelligence 12 and ego 12, otherwise the 30% check for
ordinary sentience. Then intelligence on 1d6+6, communication, languages, alignment,
powers, and ego on 1d12. The result is a
SwordSentience on the instance.
Read these tables directly when you are writing a sword by hand and want the printed odds.
communication
instance-attribute
communication: tuple[SwordCommunicationRow, ...]
How a sword of each intelligence communicates. See
SwordCommunicationRow.
languages
instance-attribute
languages: tuple[SwordTableBand, ...]
How many languages a speaking sword knows.
powers
instance-attribute
powers: tuple[SwordPowersRow, ...]
How many powers a sword of each intelligence has. See SwordPowersRow.
sensory_bands
instance-attribute
sensory_bands: tuple[SwordTableBand, ...]
Which sensory power a roll yields.
extraordinary_bands
instance-attribute
extraordinary_bands: tuple[SwordTableBand, ...]
Which extraordinary power a roll yields.
powers_catalog
instance-attribute
powers_catalog: tuple[SwordPower, ...]
Every power, with its text. See SwordPower.
special_purposes
instance-attribute
special_purposes: tuple[SwordTableBand, ...]
The purposes a special sword can be made for, like slaying a kind of creature.
special_purpose_prose
class-attribute
instance-attribute
special_purpose_prose: str = ''
The rule for the extra power a special sword brings to bear on its purpose. Show it to the referee.
alignment_touch_prose
class-attribute
instance-attribute
alignment_touch_prose: str = ''
The rule for the damage a sword deals to a bearer of the wrong alignment, which is also the only way to learn its alignment. Show it to the referee. The engine does not apply it.
power
power(power_id: str) -> SwordPower
Return the power with power_id.
Use it to turn the ids on a sword's
SwordSentience into names and text you can show.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
power_id
|
str
|
The power id, for example |
required |
Returns:
| Type | Description |
|---|---|
SwordPower
|
The power. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no power has that id. |
Examples:
SwordCommunicationRow
Bases: BaseModel
How a sentient sword of a given intelligence talks, from the Communication table.
int_score
class-attribute
instance-attribute
The sword's intelligence, 7 to 12.
reading
instance-attribute
reading: bool
True when the sword can read, which the brightest swords can.
communication
instance-attribute
communication: str
How it makes itself understood: "empathy" for a sword that only sends feelings, "speech" for one that talks.
Only a speaking sword rolls languages.
SwordControlResult
Bases: BaseModel
The arithmetic of one contest of wills between a sentient sword and its wielder.
Returned by
sword_control_check. It reports the two
totals and who won. Nothing else happens: no events, no conditions, no change to either
party. What a sword in control makes its wielder do is the referee's to narrate.
sword_controls
instance-attribute
sword_controls: bool
True when the sword's total is higher and it takes charge. A tie goes to the wielder.
SwordPower
Bases: BaseModel
One power a sentient sword can have.
Each power is text for a game to adjudicate. osrlib rolls which powers a sword has and
leaves what they do to the referee. Look one up by id with
SentientSwordTables.power, and show
prose to the referee.
id
instance-attribute
id: str
The power id, for example "detect_magic". This is what a sword's
SwordSentience records.
extraordinary
class-attribute
instance-attribute
extraordinary: bool = False
True for an extraordinary power, False for a sensory one. The two are rolled on separate tables.
duplicates_allowed
class-attribute
instance-attribute
duplicates_allowed: bool = False
True when rolling the same power twice means something, so the roll stands instead of being re-rolled.
SwordPowersRow
Bases: BaseModel
How many powers a sentient sword of a given intelligence has, from the Powers table.
int_score
class-attribute
instance-attribute
The sword's intelligence, 7 to 12.
sensory
class-attribute
instance-attribute
How many sensory powers it gets, the ones that detect things.
SwordSentience
Bases: BaseModel
What a sentient sword turned out to be: its mind, its alignment, and its powers.
Rolled once when the sword is created and fixed from then on. It lives on the sword's
MagicItemInstance. Most swords have none, and
the field is None for those. Pass the sword to
sword_control_check to find out whether it
takes charge of its wielder.
intelligence
class-attribute
instance-attribute
The sword's intelligence, 7 to 12. It sets how the sword communicates and how many powers it has.
ego
class-attribute
instance-attribute
The sword's ego, 1 to 12, or 12 for a sword of special purpose. Intelligence and ego together are what the sword brings to a contest of wills.
communication
instance-attribute
communication: str
How it makes itself understood: "empathy" or "speech".
alignment
instance-attribute
alignment: str
The sword's alignment, as a lowercase name. A bearer of a different alignment takes damage for holding it, which the rules leave to the referee to apply.
languages
class-attribute
instance-attribute
How many languages a speaking sword knows. 0 for an empathic sword.
sensory_powers
class-attribute
instance-attribute
The ids of its detecting powers. Look them up with
SentientSwordTables.power.
extraordinary_powers
class-attribute
instance-attribute
The ids of its greater powers.
special_purpose
class-attribute
instance-attribute
special_purpose: str | None = None
What the sword was made to do, for example "chaotic_creatures", or None for a sword with no special purpose.
SwordTableBand
Bases: BaseModel
One band of a sentient sword roll table: either an outcome or an instruction to roll again.
The sword tables all share this shape: a roll range and a result. The result is usually
a value, like an alignment or the id of a power, and sometimes an instruction:
roll_twice on the language and both power tables, roll_thrice on an extraordinary
result of 00, and roll_extraordinary on a high sensory roll, which trades the
sensory power for an extraordinary one.
instantiate_magic_item resolves the
instructions for you when it rolls up a sword. Read the bands yourself only to show a
referee the table.
roll_min
class-attribute
instance-attribute
The lowest roll in this band.
roll_max
class-attribute
instance-attribute
The highest roll in this band.
TreasureWeight
Bases: BaseModel
What one unit of a kind of treasure weighs, from the SRD's encumbrance table.
The rows price treasure the way the equipment lists price gear: coin and gem weigh
1 each, jewellery 10, and each magic item kind the table names has its own figure.
treasure_weight_coins reads them to weigh a
character's loot, and treasure generation stamps the gem and jewellery figures onto each
ValuableInstance it creates. The rows ship with
the equipment catalog (load_equipment) rather than the
treasure tables, because the SRD prints them on its encumbrance page.
UsableBy
Bases: BaseModel
Which characters a magic item works for.
usable_by_class answers the question this model
poses, and validate_equip applies it to devices
and miscellaneous items, rejecting with items.equip.not_usable.
Enchanted swords, weapons, and armour stay at the default all: their pages print "per
normal class restrictions", and those restrictions are the class's own armour and weapon
policies, applied to the mundane item underneath rather than here.
kind
class-attribute
instance-attribute
kind: Literal['all', 'classes', 'caster'] = 'all'
"all" for anything a character can use, "classes" to restrict to named classes, "caster" to restrict to
spell casters of a kind.
class_ids
class-attribute
instance-attribute
The classes that may use it, when kind is "classes", as ids from load_classes.
See the class id index. Empty otherwise.
caster
class-attribute
instance-attribute
caster: Literal['arcane', 'divine', 'any'] | None = None
Which kind of caster may use it, when kind is "caster": "arcane" (magic-users and elves), "divine"
(clerics), or "any". None otherwise. Wands are arcane-only. Each staff follows its own page.
ValuableInstance
Bases: BaseModel
One gem or piece of jewellery a character carries.
Treasure generation rolls the value once, when the piece is created, and it never
changes: generate_treasure returns these
alongside the coins. Selling is exact and immediate, because the tabletop rules price
treasure to feed the experience economy. Add haggling or appraisal in your own game if
you want them.
instance_type
class-attribute
instance-attribute
instance_type: Literal['valuable'] = 'valuable'
Always "valuable".
instance_id
instance-attribute
instance_id: str
This piece's own id, for example "valuable-0001", allocated by
IdAllocator.
name
class-attribute
instance-attribute
name: str = ''
A display name for the piece. Generation sets a plain one. An adventure that places treasure by hand can name it whatever it likes.
value_gp
class-attribute
instance-attribute
What it is worth in gold pieces, and what it pays when sold.
weight_coins
class-attribute
instance-attribute
The weight in coins, taken from the treasure encumbrance rows when the piece was generated. See
TreasureWeight.
VersusBonus
Bases: BaseModel
A magic weapon's bonus against particular enemies, as in +2 vs Lycanthropes.
When the target matches, this bonus replaces the item's ordinary attack and damage bonus rather than adding to it. Attack resolution reads the clause off the template. You read it to show a player what a weapon is good against.
Targets resolve structurally rather than by matching the printed label against a
monster's name: categories names tags a monster template has, like undead or
enchanted, and template_ids names compiled monster ids from
load_monsters. A clause matches a target whose template
has any of the listed tags or ids. Characters have no monster template, so a clause never
matches a character.
label
class-attribute
instance-attribute
The clause as the item's page prints it, for example "+2 vs Lycanthropes". Show this to players. Do not parse
it.
bonus
instance-attribute
bonus: int
The attack and damage bonus that applies against a matching target, replacing the item's base bonus.
categories
class-attribute
instance-attribute
Monster category tags that match, for example ("undead",).
template_ids
class-attribute
instance-attribute
Monster template ids that match. See the monster id index. At least one of categories and
template_ids is non-empty.
WeaponQuality
Bases: StrEnum
What a weapon can do in combat, as the SRD's weapon table prints it.
Every WeaponTemplate and every gear
CombatFacet has a tuple of these, and attack
resolution in osrlib.core.combat reads them: they are what makes
a bow behave differently from a mace. You never set them yourself for shipped equipment. You do
choose them when an adventure bundles a weapon of its own.
The wire values are the lowercase names below. They serialize into the compiled
equipment data and into saves, so changing one is a schema_version bump.
BLUNT
class-attribute
instance-attribute
A crushing weapon rather than an edged one. Only an edged melee weapon kills a sleeping target outright with a single hit.
BRACE
class-attribute
instance-attribute
Damage doubles when the wielder sets the weapon against a charging enemy.
CHARGE
class-attribute
instance-attribute
Damage doubles when the wielder charges with it.
MISSILE
class-attribute
instance-attribute
Usable at range. A template with this quality also has MissileRanges, and
the range band sets the attack modifier.
RELOAD
class-attribute
instance-attribute
Cannot fire two rounds running. The shot is rejected only when the weapon_reload flag of
Ruleset is on.
SLOW
class-attribute
instance-attribute
The wielder always acts after everyone not using a slow weapon, whatever initiative said.
SPLASH
class-attribute
instance-attribute
Thrown to burst on the target, so it damages again the following round unless the target douses it. Holy water and burning oil have this quality.
WeaponTemplate
Bases: BaseModel
A mundane weapon, from the SRD's weapon table.
One of the four kinds of equipment template. Get one from the shipped catalog with
EquipmentCatalog.get, then buy it with
purchase, which spawns the owned
ItemInstance a character actually carries.
Templates are frozen and shared: never mutate one, and construct one yourself only to
bundle a weapon of your own in an
Adventure.
item_type
class-attribute
instance-attribute
item_type: Literal['weapon'] = 'weapon'
Always "weapon". It is what tells the four template kinds apart when they are stored or loaded together.
id
instance-attribute
id: str
The catalog id, for example "sword". Unique across every equipment list. See the equipment id
index.
cost_gp
class-attribute
instance-attribute
The listed price in gold pieces, for one weapon.
weight_coins
class-attribute
instance-attribute
The weight in coins. For a missile weapon this already includes its ammunition and quiver, which is why ammunition itself weighs nothing.
damage
instance-attribute
damage: str
The damage the weapon deals, as a dice expression, for example "1d8". Ignored when the variable_weapon_damage
flag of Ruleset is off, which makes every weapon deal 1d6.
qualities
instance-attribute
qualities: tuple[WeaponQuality, ...]
What the weapon can do. See WeaponQuality.
missile_ranges
class-attribute
instance-attribute
missile_ranges: MissileRanges | None = None
The three range bands, present exactly when qualities includes the missile quality.
material
class-attribute
instance-attribute
What it is made of, for the silver immunity exemption. Standard unless the weapon is silvered.
encounter_movement_rate
Return how far a character moves in one combat round, in feet.
A third of the exploration rate, rounded down: that is the figure printed in brackets on the movement tables. It is computed whenever asked rather than stored, so it follows the exploration rate as a load changes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_rate_feet
|
int
|
The exploration movement rate, from
|
required |
Returns:
| Type | Description |
|---|---|
int
|
The rate in feet per round. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
equip
equip(inventory: Inventory, definition: ClassDefinition, instance: ItemInstance | MagicItemInstance) -> None
Move an item out of the item list and into use.
The à la carte way to arm a character outside a session. In a session the
EquipItem command does this and records it. Check
first with validate_equip if you would rather have
a reason than an exception.
Where the item goes depends on what it is: body armour to the worn slot, a shield to the shield slot, rings to the ring slots, and everything else that can be equipped, weapons and lit torches and wands, to the wielded list. Whatever was in the armour or shield slot goes back to the item list. The item leaves the item list, so it is in exactly one place afterwards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory. Mutated. |
required |
definition
|
ClassDefinition
|
The character's class definition, from
|
required |
instance
|
ItemInstance | MagicItemInstance
|
The item to equip. It must be in this inventory's item list. Equip what the character is holding, not a template or a copy. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the item is not in the item list, or the class cannot equip it. Ask
|
Examples:
from osrlib.core.items import CoinPurse, Inventory, equip, purchase
from osrlib.data import load_classes, load_equipment
inventory = Inventory(purse=CoinPurse(gp=50))
fighter = load_classes().get("fighter")
sword = purchase(inventory, load_equipment().get("sword"))
equip(inventory, fighter, sword)
print(len(inventory.items), [held.template.id for held in inventory.wielded])
# 0 ['sword']
equipment_weight_coins
Return the weight of a character's weapons, armour, and gear, in coins.
The other half of detailed encumbrance, alongside
treasure_weight_coins. Weapons, armour, and
ammunition weigh their listed weights, times the number carried. The shipped ammunition
templates are all weight 0, because the SRD folds ammunition into the missile weapon's
own weight, so shipped arrows and bolts add nothing. Ammunition an adventure bundles with
a weight of its own counts like any other template. Gear has no per-item weights, so
carrying any gear at all adds a flat
MISC_GEAR_WEIGHT_COINS and no more.
Enchanted weapons and armour weigh what the mundane item underneath weighs, with armour halved, since enchanted armour is lighter than the plate it is made from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory to weigh. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The equipment weight in coins. |
equipped_item_modifiers
equipped_item_modifiers(inventory: Inventory) -> list[ModifierSpec]
Return the stat modifiers a character's equipped magic items grant.
Call it wherever a bonus from an item has to be counted: attack and damage resolution, saving throws, armour class. An item contributes only while it is equipped and only if it works by being worn or wielded, so taking the ring off takes its bonus away with no bookkeeping.
Item bonuses are computed from the inventory each time you ask rather than stored as
effects, and that is deliberate: they stack freely with spell bonuses and are never
subject to the cap that
modifier_total applies to spell-sourced
modifiers, which counts only the largest bonus and the largest penalty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory to scan. |
required |
Returns:
| Type | Description |
|---|---|
list[ModifierSpec]
|
The modifiers, in equipped order: worn armour, shield, wielded, rings. Empty when |
list[ModifierSpec]
|
nothing equipped grants one. |
Examples:
from osrlib.core.items import Inventory, MagicItemInstance, equipped_item_modifiers
ring = MagicItemInstance(instance_id="magic-item-0001", template_id="ring_of_protection")
modifiers = equipped_item_modifiers(Inventory(rings=[ring]))
print([(modifier.kind, modifier.value) for modifier in modifiers])
# [('save_bonus', 1)]
magic_item_template
magic_item_template(instance: MagicItemInstance) -> MagicItemTemplate
Return the template behind a magic item instance.
The shorthand for looking the instance's template_id up in the shipped catalog: it is
what you call to get from the copy a character carries to the item's name, category,
bonuses, and text. Equivalent to
MagicItemCatalog.get on
load_magic_items, which is what to call when you have
an id rather than an instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
MagicItemInstance
|
The instance whose template to look up. |
required |
Returns:
| Type | Description |
|---|---|
MagicItemTemplate
|
The frozen template. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the instance names an item the catalog does not have. |
Examples:
movement_rate_feet
Return how far a character moves in one exploration turn, in feet.
This is the number a dungeon crawl runs on: how far a party gets on a turn of careful
movement. Character.movement_rate is
the shorthand when you have a character rather than a bare inventory. Divide by 3 with
encounter_movement_rate for the rate
inside a fight.
What decides it depends on the encumbrance flag of
Ruleset:
none: alwaysBASE_MOVEMENT_FEET. Nothing is weighed and no load limit applies.basic: the armour being worn, and whether the character is hauling treasure. Unarmoured is 120' and 90' hauling. Light armour 90' and 60'. Heavy armour 60' and 30'.detailed: the weight carried, against the printed thresholds, which are inclusive: 120' up to 400 coins, 90' up to 600, 60' up to 800, 30' up to the maximum load.
Under both tracking modes, weight past
MAX_LOAD_COINS means the character cannot move at
all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory to weigh. |
required |
ruleset
|
Ruleset
|
The ruleset whose encumbrance flag decides which rule applies. |
required |
carrying_treasure
|
bool
|
Whether the character is hauling a significant amount of treasure, under basic encumbrance. The tabletop rules leave "significant" to the referee and so does osrlib: the game sets this, and there is no invented threshold behind it. Ignored under the other two modes. |
False
|
Returns:
| Type | Description |
|---|---|
int
|
The movement rate in feet per turn: 120, 90, 60, 30, or 0. |
Examples:
from osrlib.core.items import Inventory, ItemInstance, movement_rate_feet
from osrlib.core.ruleset import Ruleset
from osrlib.data import load_equipment
plate = ItemInstance(template=load_equipment().get("plate_mail"))
inventory = Inventory(worn_armour=plate)
print(
movement_rate_feet(inventory, Ruleset()),
movement_rate_feet(inventory, Ruleset(), carrying_treasure=True),
)
# 60 30
purchase
purchase(inventory: Inventory, template: ItemTemplate, lots: int = 1) -> ItemInstance
Buy lots of an item, pay for it out of the purse, and add it to the inventory.
The à la carte way to equip a character outside a session. In a session the
PurchaseEquipment command does this and
records it. Check first with
validate_purchase if you would rather have a
reason than an exception.
A purchase lot is what one purchase at the item's listed price delivers. Gear and
ammunition come in lots of the size the catalog prints, so one purchase of torches costs
1 gp and yields six torches. Weapons and armour have no lot size, so one lot is one
item. lots multiplies both the price and what arrives: two lots of torches cost 2 gp
and yield twelve torches, in one stack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The buyer's inventory. Mutated: the purse is charged and the new stack is appended to the item list. |
required |
template
|
ItemTemplate
|
The item to buy, from
|
required |
lots
|
int
|
How many purchase lots. Positive. |
1
|
Returns:
| Type | Description |
|---|---|
ItemInstance
|
The new stack, which is also now in the inventory's item list. Pass it to |
ItemInstance
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
sword_control_check
sword_control_check(character: Character, sword: MagicItemInstance, *, stream: RngStream) -> SwordControlResult
Resolve one contest of wills between a sentient sword and the character holding it.
A sentient sword can try to take charge of its wielder. This runs that contest and reports who won. Nothing follows from it automatically, because what a controlling sword makes its wielder do is a referee's call. Nothing in the crawl calls this for you: a game decides when a sword pushes its luck, and narrates the result.
The sword's will is its intelligence plus its ego, plus 1 for each extraordinary power, plus 1d10 when wielder and sword are of different alignments. The wielder's will is strength plus wisdom, less 1d4 when they are hurt at all and 2d4 when they are below half their hit points. The sword takes charge when its total is strictly higher.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
character
|
Character
|
The wielder, a |
required |
sword
|
MagicItemInstance
|
The sword, which must have a
|
required |
stream
|
RngStream
|
The RNG stream the situational dice come from. Pass a session stream so the draws replay. Which stream is yours to choose. |
required |
Returns:
| Type | Description |
|---|---|
SwordControlResult
|
The two totals and who won. See |
SwordControlResult
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the sword is not sentient. |
Examples:
from osrlib.core.alignment import Alignment
from osrlib.core.character import CHARACTER_CREATION_STREAM, create_character
from osrlib.core.items import MagicItemInstance, SwordSentience, sword_control_check
from osrlib.core.rng import RngStreams
from osrlib.core.ruleset import Ruleset
streams = RngStreams(master_seed=7)
wielder = create_character(
name="Aleran",
class_id="fighter",
alignment=Alignment.LAWFUL,
ruleset=Ruleset(),
stream=streams.get(CHARACTER_CREATION_STREAM),
).character
sentience = SwordSentience(intelligence=12, ego=12, communication="speech", reading=True, alignment="chaotic")
sword = MagicItemInstance(
instance_id="magic-item-0001", template_id="sword_plus_1", base_item_id="sword", sentience=sentience
)
result = sword_control_check(wielder, sword, stream=streams.get("treasure"))
print(result.sword_will, result.wielder_will, result.sword_controls)
# 25 23 True
tracked_weight_coins
tracked_weight_coins(inventory: Inventory, mode: EncumbranceMode) -> int
Return the weight the encumbrance rules in play actually count, in coins.
Ask this rather than the two weighing functions directly: the answer depends on the
encumbrance flag of Ruleset, and this is what
movement_rate_feet compares against
MAX_LOAD_COINS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory to weigh. |
required |
mode
|
EncumbranceMode
|
The encumbrance mode in play, from the ruleset. |
required |
Returns:
| Type | Description |
|---|---|
int
|
0 when nothing is tracked, the treasure weight under basic encumbrance, and |
int
|
treasure plus equipment under detailed. |
Examples:
treasure_weight_coins
Return the weight of the treasure a character is carrying, in coins.
This is the figure basic encumbrance tracks: coins, gems, jewellery, and the magic items the encumbrance table prices as treasure, which are potions, scrolls, rods, staves, and wands. Every coin weighs 1, whatever its metal.
A stack of those weighs its template's figure times its quantity, and a spent stack still weighs one, because a quantity of 0 counts as 1. Rings and miscellaneous items weigh nothing, because their pages give no weight. A bag of holding weighs its printed loaded weight while it holds anything. Enchanted weapons and armour weigh as equipment beside the mundane kind rather than as treasure, so basic encumbrance stays what the rules mean by it: how much loot is being hauled out.
Call tracked_weight_coins instead when you
want whatever the ruleset in play actually tracks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory to weigh. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The treasure weight in coins. |
unequip
unequip(inventory: Inventory, instance: ItemInstance | MagicItemInstance) -> None
Take an item out of use and put it back in the item list.
The reverse of equip. In a session the
UnequipItem command does this and records it.
Check first with validate_unequip if you would
rather have a reason than an exception, since a revealed cursed item cannot be taken off
at all.
The curse check runs before the slot search, so an item under a revealed curse reports the curse whether or not it is equipped at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory. Mutated. |
required |
instance
|
ItemInstance | MagicItemInstance
|
The equipped item, from whichever slot holds it. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a revealed curse holds the item in place, and otherwise if the item is not equipped in this inventory. |
usable_by_class
usable_by_class(template: MagicItemTemplate, definition: ClassDefinition) -> bool
Return whether a character of this class can use a magic item.
The rules restrict some items to particular classes or to spell casters, and this is the
question that answers. validate_equip applies it to
devices and miscellaneous items. Call it directly when you are deciding whether to offer
a player the option to drink, read, or invoke something.
Enchanted swords, weapons, and armour are not restricted here: their pages defer to the class's ordinary armour and weapon policies, which apply to the mundane item underneath.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template
|
MagicItemTemplate
|
The magic item template. |
required |
definition
|
ClassDefinition
|
The character's class definition, from
|
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the class may use the item. |
Examples:
validate_equip
validate_equip(
definition: ClassDefinition, instance: ItemInstance | MagicItemInstance, inventory: Inventory | None = None
) -> list[Rejection]
Check whether a character of this class can equip an item.
The check half of equip, which raises where this reports.
Call it to decide what to offer a player: which weapons a magic-user can actually pick
up, why a cleric cannot draw the sword the party just found.
What it enforces:
- The class's armour policy: whether armour is allowed at all, and whether this suit is, and whether shields are.
- The class's weapon policy, which covers the weapons list only. Gear with a combat use (torch, holy water, burning oil) is exempt and always equippable, a documented adaptation that also lets a magic-user throw oil. Gear without a combat use, and ammunition, cannot be equipped by anyone.
- Two-handed weapons and shields, which cannot be used together. Whichever of the pair
comes second is rejected, so this check needs to see what is already equipped: pass
inventorywhenever you have one. - For magic items: enchanted arms resolve through the policies of the mundane item
underneath, rings against the two-ring cap, and devices and miscellaneous items
against their own usability. See
usable_by_class. Potions and scrolls are not equipment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
definition
|
ClassDefinition
|
The character's class definition, from
|
required |
instance
|
ItemInstance | MagicItemInstance
|
The item to equip. |
required |
inventory
|
Inventory | None
|
The inventory whose equipped state the two-handed-and-shield check reads. Leave it out only when there is no inventory yet. |
None
|
Returns:
| Type | Description |
|---|---|
list[Rejection]
|
The reasons it cannot be equipped, as |
list[Rejection]
|
|
list[Rejection]
|
are |
list[Rejection]
|
|
list[Rejection]
|
|
list[Rejection]
|
|
list[Rejection]
|
|
Examples:
from osrlib.core.items import ItemInstance, validate_equip
from osrlib.data import load_classes, load_equipment
catalog = load_equipment()
cleric = load_classes().get("cleric")
print([rejection.code for rejection in validate_equip(cleric, ItemInstance(template=catalog.get("sword")))])
# ['items.equip.weapon_not_allowed']
print([rejection.code for rejection in validate_equip(cleric, ItemInstance(template=catalog.get("mace")))])
# []
validate_purchase
validate_purchase(purse: CoinPurse, template: ItemTemplate, lots: int = 1) -> list[Rejection]
Check whether a character can afford to buy lots of an item.
The check half of purchase, which raises where this
reports. Call this when you want a reason to show a player rather than an exception:
a shop screen that greys out what the party cannot afford, or a command that rejects.
A purchase lot is what one purchase at the item's listed price delivers. See
purchase for what a lot buys.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
purse
|
CoinPurse
|
The buyer's purse. |
required |
template
|
ItemTemplate
|
The item to buy. |
required |
lots
|
int
|
How many purchase lots. Positive. |
1
|
Returns:
| Type | Description |
|---|---|
list[Rejection]
|
The reasons the purchase cannot go ahead, as |
list[Rejection]
|
|
list[Rejection]
|
reason here is |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
validate_unequip
validate_unequip(inventory: Inventory, instance: ItemInstance | MagicItemInstance) -> list[Rejection]
Check whether an equipped item can be taken off.
The check half of unequip, which raises where this
reports. There is one reason it can fail: a cursed item whose curse has shown itself
sticks to its bearer until remove curse, and every cursed item's page says so.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inventory
|
Inventory
|
The inventory holding the item. |
required |
instance
|
ItemInstance | MagicItemInstance
|
The equipped item. |
required |
Returns:
| Type | Description |
|---|---|
list[Rejection]
|
The reasons it cannot come off, as |
list[Rejection]
|
|
list[Rejection]
|
reason is |
Examples:
from osrlib.core.items import Inventory, MagicItemInstance, validate_unequip
ring = MagicItemInstance(instance_id="magic-item-0001", template_id="ring_of_weakness", cursed_revealed=True)
inventory = Inventory(rings=[ring])
print([rejection.code for rejection in validate_unequip(inventory, ring)])
# ['items.curse.stuck']