Skip to content

osrlib.core.treasure

Treasure tables and treasure generation: the types monsters carry, gems, jewellery, and magic items.

This module turns a treasure type into loot. It takes the compiled tables that load_treasure_tables returns and produces a GeneratedTreasure: coins, gems and jewellery, and magic item instances ready for an Inventory or a chest in a dungeon.

Which entry point you want depends on what you are stocking:

Under a running game, treasure is generated for you when a monster is placed or a cache is stocked, and reaches the party through the TakeTreasure command. Call these functions directly when you are using the rules without a session, or building an adventure ahead of play.

Every entry point takes a tier, either "basic" or "expert". The rules print two probability columns for magic items, B and X, one for each half of the game, and the tier picks the column: a game chooses by how experienced the party is, and the crawl uses Basic while the party's highest living level is 1 to 3 and Expert from 4 up.

Every printed entry of a treasure type parses to one fixed shape: an optional percentage chance that the entry is there at all, then either a quantity of coins, a count of gems or jewellery, or a magic item allotment. Allotments are structured rather than free text, so "any 3 magic items" and "1 potion" and "a sword, suit of armour, or weapon" are all MagicAllotments a program can read.

Generation draws in printed order: the presence roll for each entry, then the quantity dice, then each item resolved completely before the next one starts. That order is the contract, so the same seed and the same table always produce the same hoard.

Typical usage:

from osrlib.core.monsters import IdAllocator
from osrlib.core.rng import RngStreams
from osrlib.core.treasure import TREASURE_STREAM, generate_treasure

stream = RngStreams(master_seed=1).get(TREASURE_STREAM)
hoard = generate_treasure("B", tier="basic", stream=stream, allocator=IdAllocator())

print(hoard.coins.value_gp, len(hoard.valuables), len(hoard.magic_items))
# 3000 4 0

TREASURE_STREAM module-attribute

TREASURE_STREAM = StreamName.TREASURE

The name of the RNG stream treasure generation draws from.

Pass streams.get(TREASURE_STREAM) to any generation function, where streams is the RngStreams of the session or of your own master seed. Using the named stream is what makes a hoard reproducible: every stream advances independently, so the treasure a party finds does not change because a fight went differently.

Nothing forces the choice, and the generation functions take whatever stream you hand them. Use this one unless you have a reason not to.

CoinDenomination

Bases: StrEnum

The five coin denominations, as the treasure tables print them.

A coin entry names one of these, and generation adds the rolled amount to the matching field of Coins. The values are the same strings those models use for their fields, so a denomination can be used as a key.

PP class-attribute instance-attribute

PP = 'pp'

Platinum, worth 5 gp.

GP class-attribute instance-attribute

GP = 'gp'

Gold.

EP class-attribute instance-attribute

EP = 'ep'

Electrum, worth half a gold piece.

SP class-attribute instance-attribute

SP = 'sp'

Silver, ten to the gold piece.

CP class-attribute instance-attribute

CP = 'cp'

Copper, a hundred to the gold piece.

CoinQuantity

Bases: BaseModel

How many coins of one denomination a treasure entry yields.

The printed multiplier is folded into the dice expression, so an entry that reads 1d6 × 1,000 gp arrives here as dice of 1d6×1000 and a denomination of gold. Roll it with roll if you are generating by hand. The generation functions do it for you.

denomination instance-attribute

denomination: CoinDenomination

Which coins. See CoinDenomination.

dice instance-attribute

dice: str

How many, as a dice expression.

GemValueBand

Bases: BaseModel

One band of the gem value table: the d20 rolls that make a gem worth a given amount.

roll_min class-attribute instance-attribute

roll_min: int = Field(ge=1, le=20)

The lowest d20 result in this band.

roll_max class-attribute instance-attribute

roll_max: int = Field(ge=1, le=20)

The highest d20 result in this band.

value_gp class-attribute instance-attribute

value_gp: int = Field(ge=1)

What a gem rolled in this band is worth, in gold pieces.

GemValueTable

Bases: BaseModel

What a gem is worth, and how much a piece of jewellery is worth.

Generation rolls a gem's value on the d20 bands and a piece of jewellery on the dice, once per piece, and stamps the result onto a ValuableInstance whose value never changes afterwards. Roll a gem yourself with value_for_roll.

bands class-attribute instance-attribute

bands: tuple[GemValueBand, ...] = Field(min_length=1)

The gem value bands, covering the whole d20. See GemValueBand.

jewellery_dice instance-attribute

jewellery_dice: str

What one piece of jewellery is worth, as a dice expression.

manual_notes class-attribute instance-attribute

manual_notes: tuple[str, ...] = ()

The rules the referee applies by hand, as printed: that rough treatment can halve a piece of jewellery's value, and that several gems may be combined into fewer, larger ones. osrlib does neither. Show these to the referee if your game applies them.

value_for_roll

value_for_roll(roll: int) -> int

Return what a gem is worth for a d20 roll.

Parameters:

Name Type Description Default
roll int

The d20 result, 1 to 20.

required

Returns:

Type Description
int

The value in gold pieces.

Raises:

Type Description
ValueError

If the roll is outside 1 to 20.

Examples:

from osrlib.data import load_treasure_tables

gems = load_treasure_tables().gems
print(gems.value_for_roll(1), gems.value_for_roll(20))
# 10 1000

MagicAllotment

Bases: BaseModel

One magic item clause of a treasure entry: how many items, and how free the choice is.

Read kind to know what the clause means:

  • any: roll the master Magic Item Type table for each item, re-rolling anything in exclude. This is how "any 3 magic items" and "any 1 magic item, no weapons" are stored.
  • category: roll the one table named in categories, as in "1 potion".
  • pool: choose evenly among the types in categories, then roll that type's table, as in "a magic sword, suit of armour, or weapon".

Exactly one of count and count_dice sizes the clause. Only an any clause has exclusions, because a clause that names its type has nothing to exclude.

kind instance-attribute

kind: Literal['any', 'category', 'pool']

"any", "category", or "pool".

categories class-attribute instance-attribute

categories: tuple[MagicItemType, ...] = ()

The types the clause names. See MagicItemType. Empty for any, one for category, two or more for pool.

count class-attribute instance-attribute

count: int | None = None

A fixed number of items, or None when the count is rolled.

count_dice class-attribute instance-attribute

count_dice: str | None = None

The number of items as a dice expression, or None when it is fixed.

exclude class-attribute instance-attribute

exclude: tuple[MagicItemType, ...] = ()

Types an any clause re-rolls. Empty on the other kinds.

MagicItemType

Bases: StrEnum

The types of the master Magic Item Type table, which is the first roll in generating a magic item.

Roll the master table to get one of these, then roll that type's own table for the item: generate_magic_item does both. A magic allotment for one kind of item names its type here.

These are the table's types, not the catalog's categories. Rods, staves, and wands share a single printed row and a single table, while the catalog keeps them apart. See MagicItemCategory.

The wire values are lowercase and serialize into the compiled treasure data. Changing them is a schema_version bump.

ARMOUR class-attribute instance-attribute

ARMOUR = 'armour'

Enchanted armour and shields.

MISC class-attribute instance-attribute

MISC = 'misc'

Miscellaneous magic items.

POTION class-attribute instance-attribute

POTION = 'potion'

Potions.

RING class-attribute instance-attribute

RING = 'ring'

Rings.

ROD_STAFF_WAND class-attribute instance-attribute

ROD_STAFF_WAND = 'rod_staff_wand'

The rod, staff, and wand row, which covers all three.

SCROLL class-attribute instance-attribute

SCROLL = 'scroll'

The scroll row, which covers treasure maps as well as spell scrolls.

SWORD class-attribute instance-attribute

SWORD = 'sword'

Enchanted swords.

WEAPON class-attribute instance-attribute

WEAPON = 'weapon'

Every other enchanted weapon.

MagicItemTypeRow

Bases: BaseModel

One row of the master Magic Item Type table: a type and the d% rolls that select it, in both columns.

The rules print two columns, B for Basic play and X for Expert, and a type's odds differ between them: scrolls and swords get likelier in Expert play, potions less likely. A printed 00 is read as 100, so both columns close at 100.

category instance-attribute

category: MagicItemType

The type this row selects. See MagicItemType.

basic_min class-attribute instance-attribute

basic_min: int = Field(ge=1, le=100)

The lowest d% roll that selects it in the B column.

basic_max class-attribute instance-attribute

basic_max: int = Field(ge=1, le=100)

The highest d% roll that selects it in the B column.

expert_min class-attribute instance-attribute

expert_min: int = Field(ge=1, le=100)

The lowest d% roll that selects it in the X column.

expert_max class-attribute instance-attribute

expert_max: int = Field(ge=1, le=100)

The highest d% roll that selects it in the X column.

MagicItemTypeTable

Bases: BaseModel

The master Magic Item Type table: which kind of magic item a random roll produces.

The first of the two rolls that generate a magic item. Roll it with category_for_roll, then roll the type's own table, which MagicItemCatalog.sub_table returns. Or let generate_magic_item do both and instantiate the item.

rows class-attribute instance-attribute

rows: tuple[MagicItemTypeRow, ...] = Field(min_length=1)

The rows, in printed order, each column covering the whole d%. See MagicItemTypeRow.

category_for_roll

category_for_roll(roll: int, *, tier: str) -> MagicItemType

Return the type of magic item a d% roll produces, under one tier's column.

Parameters:

Name Type Description Default
roll int

The d% result, 1 to 100, with a rolled 00 passed as 100.

required
tier str

"basic" or "expert", choosing the printed B or X column.

required

Returns:

Type Description
MagicItemType

The selected type.

Raises:

Type Description
ValueError

If the tier is not one of the two, or the roll is outside 1 to 100.

Examples:

from osrlib.data import load_treasure_tables

table = load_treasure_tables().magic_item_types
print(table.category_for_roll(50, tier="basic"), table.category_for_roll(50, tier="expert"))
# rod_staff_wand scroll

RoomContentsResult

Bases: BaseModel

What one roll on the room stocking table produced.

Returned by roll_room_contents. It reports both rolls so an adventure author can see how a room was decided, and so a tool that stocks a level can log it.

roll instance-attribute

roll: int

The d6 that chose the contents.

row instance-attribute

The row it selected. See StockingRow.

treasure_roll class-attribute instance-attribute

treasure_roll: int | None = None

The d6 rolled for treasure, or None when the row gives no chance of treasure and no die was rolled.

treasure_present class-attribute instance-attribute

treasure_present: bool = False

True when the room has treasure as well as its contents. Which treasure is yours to decide: generate it with generate_unguarded_treasure for an empty or trapped room, or from the monster's own type when a monster is there.

StockingRow

Bases: BaseModel

One row of the room stocking table: what is in a room, and how likely treasure is with it.

roll_min class-attribute instance-attribute

roll_min: int = Field(ge=1, le=6)

The lowest d6 result in this row.

roll_max class-attribute instance-attribute

roll_max: int = Field(ge=1, le=6)

The highest d6 result in this row.

contents instance-attribute

contents: Literal['empty', 'monster', 'special', 'trap']

What is in the room: "empty", "monster", "special", or "trap".

treasure_chance_in_six class-attribute instance-attribute

treasure_chance_in_six: int = Field(ge=0, le=6)

How many faces of a d6 mean treasure as well, for example 3 for a 3-in-6 chance. 0 where the table prints no chance, and no die is rolled then.

StockingTable

Bases: BaseModel

The Random Dungeon Room Contents table: what to put in a room when you are stocking a dungeon.

Rolling it is a two-step procedure, so call roll_room_contents rather than this table directly unless you are rolling by hand: it rolls the contents, then the treasure chance that goes with them, and reports both.

This is an authoring tool, not something play calls: it is how you fill a dungeon level before anyone explores it.

rows class-attribute instance-attribute

rows: tuple[StockingRow, ...] = Field(min_length=1)

The rows, covering the whole d6. See StockingRow.

row_for_roll

row_for_roll(roll: int) -> StockingRow

Return the stocking row a d6 roll selects.

Parameters:

Name Type Description Default
roll int

The d6 result, 1 to 6.

required

Returns:

Type Description
StockingRow

The selected row.

Raises:

Type Description
ValueError

If the roll is outside 1 to 6.

TreasureEntry

Bases: BaseModel

One printed line of a treasure type: one thing that might be in the hoard.

A treasure type is a list of these, and generation walks them in order. Each has exactly one payload, so an entry is coins, or gems, or jewellery, or magic items, never a mixture. Generate a list of them with generate_treasure_entries, which is also how you generate the hoard a treasure map leads to.

chance_pct class-attribute instance-attribute

chance_pct: int = Field(default=0, ge=0, le=100)

The percentage chance the entry is present at all. 0 means it always is, which is how the entries printed without a chance are stored.

coins class-attribute instance-attribute

coins: CoinQuantity | None = None

The coins this entry yields, or None. See CoinQuantity.

gems_dice class-attribute instance-attribute

gems_dice: str | None = None

How many gems, as a dice expression, or None. Each gem's value is rolled separately on the gem table.

jewellery_dice class-attribute instance-attribute

jewellery_dice: str | None = None

How many pieces of jewellery, as a dice expression, or None. Each piece's value is rolled separately.

magic class-attribute instance-attribute

magic: tuple[MagicAllotment, ...] = ()

The magic item clauses, or empty. See MagicAllotment.

TreasureRefPlan

Bases: BaseModel

A monster's treasure entry, worked out into what to generate and when.

plan_treasure_ref produces one of these from a stat block's treasure reference. The three letter lists say when each letter is generated: the lair ones once when you stock the lair, the individual ones once per monster, and the group ones once for the whole group. A letter in parentheses on the stat block is lair treasure whatever section it belongs to, which is how the bandit's U (A) means each bandit carries type U and the camp has a type A hoard.

Some references generate nothing at all. Where a stat block marks the treasure special or describes it below, the referee writes it, and those parts are left out of the plan. So are the two adjustments the rules leave to the referee: reducing a hoard for a small lair, and changing a hoard's value by hand.

lair class-attribute instance-attribute

lair: tuple[str, ...] = ()

Letters to generate once for the lair, in the order the stat block lists them.

individual class-attribute instance-attribute

individual: tuple[str, ...] = ()

Letters to generate once per monster.

group class-attribute instance-attribute

group: tuple[str, ...] = ()

Letters to generate once for the group.

extra_gp class-attribute instance-attribute

extra_gp: int = 0

Flat gold pieces the entry adds to the lair hoard.

multiplier class-attribute instance-attribute

multiplier: int = 1

How many times to run the whole generation, for an entry like the noble's V × 3. 1 for everything else.

TreasureSection

Bases: StrEnum

Which section of the treasure tables a letter belongs to, which is when you generate it.

A monster's stat block names treasure types by letter, and when you generate a letter depends on its section: once for the lair, once per monster, or once for the whole group. plan_treasure_ref sorts a stat block's letters into those three piles for you.

The wire values are lowercase and serialize into the compiled treasure data. Changing them is a schema_version bump.

HOARD class-attribute instance-attribute

HOARD = 'hoard'

Types A to O: the treasure in a monster's lair, generated once for the lair.

INDIVIDUAL class-attribute instance-attribute

INDIVIDUAL = 'individual'

Types P to T: what one monster carries, generated once per monster.

GROUP class-attribute instance-attribute

GROUP = 'group'

Types U and V: what a group carries between them, generated once for the group.

TreasureTables

Bases: BaseModel

Every treasure table there is, loaded and ready to generate from.

Call load_treasure_tables to get the shipped set. It is frozen, cached, and shared. The generation functions load it themselves, so you need this only to read a table: to show a referee what a letter can produce, or to roll a table by hand.

treasure_types instance-attribute

treasure_types: tuple[TreasureTypeTable, ...]

Every treasure type, A through V. See TreasureTypeTable. Reach one by letter with treasure_type.

gems instance-attribute

What gems and jewellery are worth. See GemValueTable.

magic_item_types instance-attribute

magic_item_types: MagicItemTypeTable

Which kind of magic item a roll produces. See MagicItemTypeTable.

stocking instance-attribute

stocking: StockingTable

What is in a room when you stock a dungeon. See StockingTable.

unguarded instance-attribute

What an unguarded cache contains, by level. See UnguardedTreasureTable.

treasure_type

treasure_type(letter: str) -> TreasureTypeTable

Return the treasure type for letter.

Parameters:

Name Type Description Default
letter str

The type letter, for example "A", as a monster's stat block names it. See the treasure type index. Case-sensitive: the letters are uppercase.

required

Returns:

Type Description
TreasureTypeTable

The treasure type.

Raises:

Type Description
ValueError

If no type has that letter.

Examples:

from osrlib.data import load_treasure_tables

hoard = load_treasure_tables().treasure_type("A")
print(hoard.kind, hoard.average_gp)
# hoard 18000.0

TreasureTypeTable

Bases: BaseModel

One treasure type, A through V: everything a hoard of that letter can contain.

Get one with TreasureTables.treasure_type, or skip straight to the result with generate_treasure. Read the entries when you want to show a referee what a letter can produce before rolling it.

letter class-attribute instance-attribute

letter: str = Field(min_length=1, max_length=1)

The type letter, for example "A". See the treasure type index.

kind instance-attribute

Whether the letter is lair treasure, carried by one monster, or carried by a group. See TreasureSection.

average_gp class-attribute instance-attribute

average_gp: float = Field(ge=0)

The average value of a hoard of this type in gold pieces, as the rules print it. A planning figure for an adventure author, not something generation aims at.

entries class-attribute instance-attribute

entries: tuple[TreasureEntry, ...] = Field(min_length=1)

The printed lines, in order. See TreasureEntry.

UnguardedTreasureBand

Bases: BaseModel

One dungeon-level band of the unguarded treasure table.

Deeper levels have more, so the table is banded by level. The entries have the same shape as a treasure type's.

label class-attribute instance-attribute

label: str = Field(min_length=1)

The band as the table prints it, for example "Level 2–3".

min_level class-attribute instance-attribute

min_level: int = Field(ge=1)

The shallowest dungeon level in the band.

max_level class-attribute instance-attribute

max_level: int = Field(ge=1)

The deepest dungeon level in the band.

entries class-attribute instance-attribute

entries: tuple[TreasureEntry, ...] = Field(min_length=1)

The printed lines, in order. See TreasureEntry.

UnguardedTreasureTable

Bases: BaseModel

The unguarded treasure table: what a cache nobody is guarding contains, by dungeon level.

Use it through generate_unguarded_treasure, which picks the band and generates from it. Levels past the deepest printed band use that band, so a level 12 cache is as rich as a level 9 one and no richer.

bands class-attribute instance-attribute

bands: tuple[UnguardedTreasureBand, ...] = Field(min_length=1)

The bands, in level order and contiguous from level 1. See UnguardedTreasureBand.

band_for_level

band_for_level(level: int) -> UnguardedTreasureBand

Return the band for a dungeon level.

Parameters:

Name Type Description Default
level int

The dungeon level, counting from 1.

required

Returns:

Type Description
UnguardedTreasureBand

The band covering that level. Levels past the deepest band get the deepest band,

UnguardedTreasureBand

the same clamp the encounter tables use.

Raises:

Type Description
ValueError

If level is below 1.

generate_magic_item

generate_magic_item(
    category: MagicItemType | None,
    *,
    tier: str,
    stream: RngStream,
    allocator: Any,
    exclude: tuple[MagicItemType, ...] = ()
) -> list[MagicItemInstance]

Roll one magic item at random: which kind, which item, and all its details.

Three steps in one call. When category is None it rolls the master Magic Item Type table for the kind, re-rolling anything you excluded. Then it rolls that kind's own table for the item. Then instantiate_magic_item rolls the item's details. Pass a category when the kind is already decided, as it is for an allotment for a potion.

Use it when you want a single random item: a reward, a gift, a hand-placed surprise. For a monster's whole hoard call generate_treasure, which rolls allotments as well as coins and gems.

Parameters:

Name Type Description Default
category MagicItemType | None

The kind of item to roll, or None to roll the kind too. See MagicItemType.

required
tier str

"basic" or "expert", the printed B or X column. It changes the odds of each kind and which items a table can reach.

required
stream RngStream

The RNG stream to draw from, conventionally TREASURE_STREAM.

required
allocator Any

The id source for the new instances, which take the magic-item prefix. An IdAllocator.

required
exclude tuple[MagicItemType, ...]

Kinds to re-roll when the kind is being rolled. Excluded rolls consume draws, as re-rolling at the table does.

()

Returns:

Type Description
list[MagicItemInstance]

The new instances, unidentified. Usually one. Two when the table's row is a suit of

list[MagicItemInstance]

armour that comes with a shield.

Raises:

Type Description
ValueError

If tier is neither "basic" nor "expert", or category is one of the kinds you excluded.

Examples:

from osrlib.core.monsters import IdAllocator
from osrlib.core.rng import RngStreams
from osrlib.core.treasure import TREASURE_STREAM, generate_magic_item

stream = RngStreams(master_seed=9).get(TREASURE_STREAM)
items = generate_magic_item(None, tier="basic", stream=stream, allocator=IdAllocator())

print([(item.instance_id, item.template_id) for item in items])
# [('magic-item-0001', 'rope_of_climbing')]

generate_treasure

generate_treasure(treasure_type: str, *, tier: str, stream: RngStream, allocator: Any) -> GeneratedTreasure

Generate one treasure type's contents.

The entry point for a monster's treasure: a stat block names a letter, and this rolls everything that letter can contain. Which letters to roll, and whether each belongs to the lair, one monster, or the group, is what plan_treasure_ref works out from the stat block.

Nothing is placed: what comes back is yours to put in a chest, hand to a monster, or add to an inventory.

Parameters:

Name Type Description Default
treasure_type str

The type letter, "A" through "V". See the treasure type index.

required
tier str

"basic" or "expert", the printed B or X column for magic items. A game chooses by how experienced the party is. The crawl uses Basic while the party's highest living level is 1 to 3 and Expert from 4 up, decided when the treasure is generated.

required
stream RngStream

The RNG stream to draw from, conventionally TREASURE_STREAM.

required
allocator Any

The id source for generated gems, jewellery, and magic items, which take the valuable and magic-item prefixes. An IdAllocator.

required

Returns:

Type Description
GeneratedTreasure

The coins, valuables, and magic items. See

GeneratedTreasure

GeneratedTreasure. An unlucky hoard can be

GeneratedTreasure

empty.

Raises:

Type Description
ValueError

If no treasure type has that letter, or if tier is neither "basic" nor "expert". Both are checked before the first draw, so a refused call costs no draws and leaves the stream where it was.

Examples:

from osrlib.core.monsters import IdAllocator
from osrlib.core.rng import RngStreams
from osrlib.core.treasure import TREASURE_STREAM, generate_treasure

stream = RngStreams(master_seed=1).get(TREASURE_STREAM)
hoard = generate_treasure("A", tier="expert", stream=stream, allocator=IdAllocator())
assert hoard.coins.total_coins == 7000
assert len(hoard.valuables) == 19
assert {item.template_id for item in hoard.magic_items} == {
    "sword_plus_1_plus_3_vs_dragons",
    "ring_of_protection",
    "potion_of_poison",
}

generate_treasure_entries

generate_treasure_entries(
    entries: Sequence[TreasureEntry], *, tier: str, stream: RngStream, allocator: Any
) -> GeneratedTreasure

Generate treasure from a list of printed entries.

The engine the other generation functions share, and the one to call when you have entries in hand rather than a letter or a level: the hoard a treasure map leads to is a list of entries on the map's own template, and this is how you turn it into loot.

Entries are resolved in printed order: for each one, the presence roll if it is gated, then the quantity dice, then each gem, piece of jewellery, or magic item resolved completely before the next begins. That order is the contract behind reproducibility, so the same seed and the same entries always give the same result.

Parameters:

Name Type Description Default
entries Sequence[TreasureEntry]

The printed entries. See TreasureEntry.

required
tier str

"basic" or "expert", the printed B or X column for magic items.

required
stream RngStream

The RNG stream to draw from, conventionally TREASURE_STREAM.

required
allocator Any

The id source for generated gems, jewellery, and magic items, which take the valuable and magic-item prefixes. An IdAllocator.

required

Returns:

Type Description
GeneratedTreasure

The coins, valuables, and magic items. See

GeneratedTreasure

GeneratedTreasure. Any of the three can be

GeneratedTreasure

empty, and entries that failed their presence roll contribute nothing.

Raises:

Type Description
ValueError

If tier is neither "basic" nor "expert". The tier is checked before the first draw, so a refused call costs no draws and leaves the stream where it was, whether or not the entries would have reached a magic item.

Examples:

from osrlib.core.monsters import IdAllocator
from osrlib.core.rng import RngStreams
from osrlib.core.treasure import TREASURE_STREAM, generate_treasure_entries
from osrlib.data import load_magic_items

recipe = load_magic_items().get("treasure_map_i").hoard_recipe
stream = RngStreams(master_seed=2).get(TREASURE_STREAM)
hoard = generate_treasure_entries(recipe, tier="basic", stream=stream, allocator=IdAllocator())

print([item.template_id for item in hoard.magic_items])
# ['mace_plus_1']

generate_unguarded_treasure

generate_unguarded_treasure(dungeon_level: int, *, tier: str, stream: RngStream, allocator: Any) -> GeneratedTreasure

Generate a cache of treasure nobody is guarding, for a dungeon level.

The counterpart to generate_treasure: use it when the treasure belongs to the room rather than to a monster, which is what roll_room_contents reports when an empty or trapped room turns out to have something.

Deeper levels have more. Levels past the deepest printed band use that band, so treasure stops getting richer below level 9.

Parameters:

Name Type Description Default
dungeon_level int

The dungeon level, counting from 1.

required
tier str

"basic" or "expert", the printed B or X column for magic items.

required
stream RngStream

The RNG stream to draw from, conventionally TREASURE_STREAM.

required
allocator Any

The id source for generated gems, jewellery, and magic items. An IdAllocator.

required

Returns:

Type Description
GeneratedTreasure

The coins, valuables, and magic items. See

GeneratedTreasure

GeneratedTreasure. Most caches are coins

GeneratedTreasure

alone.

Raises:

Type Description
ValueError

If dungeon_level is below 1, or if tier is neither "basic" nor "expert". Both are checked before the first draw, so a refused call costs no draws and leaves the stream where it was.

Examples:

from osrlib.core.monsters import IdAllocator
from osrlib.core.rng import RngStreams
from osrlib.core.treasure import TREASURE_STREAM, generate_unguarded_treasure

stream = RngStreams(master_seed=3).get(TREASURE_STREAM)
cache = generate_unguarded_treasure(1, tier="basic", stream=stream, allocator=IdAllocator())

print(cache.coins.sp, cache.coins.value_gp, len(cache.magic_items))
# 300 30 0

instantiate_magic_item

instantiate_magic_item(
    item_id: str, *, tier: str, stream: RngStream, allocator: Any, params: Mapping[str, Any] | None = None
) -> MagicItemInstance

Create one copy of a magic item you have already chosen, rolling the details that differ between copies.

This is how a hand-placed magic item becomes something a character can carry. An adventure names the item (FeatureSpec.magic_item_ids does exactly this), and this rolls what the item's own page leaves to chance: what a generic suit of enchanted armour is made of, how many charges a wand has, how many arrows are in the bundle, how many wishes the ring has, which spells are on the scroll, how many levels the sword can drain, and last of all whether a sword is sentient. Nothing is left unrolled: the instance that comes back is ready to use.

When you want the item chosen at random too, call generate_magic_item, which rolls the tables and then calls this.

The copy comes back unidentified. The party learns what it is by using it.

Parameters:

Name Type Description Default
item_id str

The item to create. See the magic item id index.

required
tier str

"basic" or "expert", the printed B or X column. It matters only where a detail differs between the two, like the level of a scroll's spells.

required
stream RngStream

The RNG stream to draw from, conventionally TREASURE_STREAM.

required
allocator Any

The id source for the new instance's id, which takes the magic-item prefix. An IdAllocator. A session hands you its own, and a standalone caller makes one.

required
params Mapping[str, Any] | None

Overrides from a generation table row, when this is being called from one: the quantity dice a printed band gives, a fixed Basic-tier quantity, a wish count. Leave it None when placing an item by hand, and the template's own values are used.

None

Returns:

Type Description
MagicItemInstance

The new instance, unidentified. See

MagicItemInstance

Raises:

Type Description
ValueError

If tier is neither "basic" nor "expert", or the catalog has no item with that id.

Examples:

from osrlib.core.monsters import IdAllocator
from osrlib.core.rng import RngStreams
from osrlib.core.treasure import TREASURE_STREAM, instantiate_magic_item

stream = RngStreams(master_seed=11).get(TREASURE_STREAM)
staff = instantiate_magic_item("staff_of_striking", tier="expert", stream=stream, allocator=IdAllocator())

print(staff.instance_id, staff.charges_remaining, staff.identified)
# magic-item-0001 18 False

plan_treasure_ref

plan_treasure_ref(ref: TreasureRef) -> TreasureRefPlan

Work out what a monster's treasure entry means.

Call this before generating a monster's treasure: the letters on a stat block are not all generated at the same moment, and this sorts them into the ones you roll for the lair, the ones you roll per monster, and the ones you roll for the group. Then call generate_treasure once per letter, repeated multiplier times.

Parameters:

Name Type Description Default
ref TreasureRef

The treasure reference from a monster template's treasure field. See TreasureRef.

required

Returns:

Type Description
TreasureRefPlan

The plan. See TreasureRefPlan.

Raises:

Type Description
ValueError

If the reference names a letter that is not a treasure type.

Examples:

from osrlib.core.treasure import plan_treasure_ref
from osrlib.data import load_monsters

plan = plan_treasure_ref(load_monsters().get("bandit").treasure)
print(plan.lair, plan.group, plan.individual)
# ('A',) ('U',) ()

roll_room_contents

roll_room_contents(stream: RngStream) -> RoomContentsResult

Roll what is in a room and whether there is treasure with it.

The stocking roll, for filling a dungeon level before play: it rolls the d6 for contents, then, when the row gives a chance of treasure, a second d6 for that. A row printing no chance of treasure consumes no second die, which keeps the draw sequence exact.

Generating the treasure is a separate step, because which table to use depends on what is in the room: the monster's own treasure type when a monster is there, and generate_unguarded_treasure otherwise.

Parameters:

Name Type Description Default
stream RngStream

The RNG stream to draw from, conventionally TREASURE_STREAM.

required

Returns:

Type Description
RoomContentsResult

Both rolls and the row they selected. See

RoomContentsResult

Examples:

from osrlib.core.rng import RngStreams
from osrlib.core.treasure import TREASURE_STREAM, roll_room_contents

result = roll_room_contents(RngStreams(master_seed=5).get(TREASURE_STREAM))
print(result.roll, result.row.contents, result.treasure_present)
# 6 trap False