Skip to content

osrlib.core.creature

The attribute surface a character or a monster instance offers the rules, as protocols.

A rules function in osrlib.core.combat, osrlib.core.spells, and osrlib.core.effects takes the creature it acts on as one of the three protocols here, unless what it reads needs a single concrete type, in which case it takes that type and says so in its own entry. A Character and a MonsterInstance satisfy the protocols structurally, so you pass either without a cast, and pyright checks that whatever else you pass has the attributes the function reads. Nothing here is instantiated. Read a protocol to learn what a function needs from its argument, and annotate your own code with it when you write a function that takes either kind of creature.

Creature is the base: an id, a name, hit points, conditions, and stat modifiers. Combatant adds the combat numbers an attack or a saving throw reads. Caster adds what memorizing and casting read. A function that needs an attribute only one concrete type has, such as a monster's template or a character's inventory, either takes that concrete type or reads the attribute with getattr and a default, and its docstring says which.

Typical usage:

from osrlib.core.alignment import Alignment
from osrlib.core.character import CHARACTER_CREATION_STREAM, create_character
from osrlib.core.creature import Combatant, Creature
from osrlib.core.monsters import MONSTER_SPAWN_STREAM, spawn_monster
from osrlib.core.rng import RngStreams
from osrlib.core.ruleset import Ruleset
from osrlib.data import load_monsters

streams = RngStreams(master_seed=7)
hero = create_character(
    name="Hild",
    class_id="fighter",
    alignment=Alignment.LAWFUL,
    ruleset=Ruleset(),
    stream=streams.get(CHARACTER_CREATION_STREAM),
).character
goblin = spawn_monster(load_monsters().get("goblin"), id="monster-0001", stream=streams.get(MONSTER_SPAWN_STREAM))

def hurt(creature: Creature) -> bool:
    return creature.current_hp < creature.max_hp

def can_be_hit(combatant: Combatant) -> bool:
    return combatant.armour_class is not None

print(hurt(hero), hurt(goblin), can_be_hit(hero), can_be_hit(goblin))
# False False True True

Caster

Bases: Creature, Protocol

A creature that memorizes and casts spells.

memorize_spells, validate_cast, cast_spell, and disrupt_casting take this. A Character satisfies it and a MonsterInstance does not, because monsters in the compiled data never cast. The class that decides which list and how many slots is not part of the surface: those functions take the ClassDefinition or the CasterProfile as a separate argument.

Examples:

from osrlib.core.alignment import Alignment
from osrlib.core.character import CHARACTER_CREATION_STREAM, create_character
from osrlib.core.creature import Caster
from osrlib.core.rng import RngStreams
from osrlib.core.ruleset import Ruleset

stream = RngStreams(master_seed=7).get(CHARACTER_CREATION_STREAM)
zelia = create_character(
    name="Zelia",
    class_id="magic_user",
    alignment=Alignment.NEUTRAL,
    ruleset=Ruleset(),
    stream=stream,
    starting_spell_ids=["sleep"],
).character
print(isinstance(zelia, Caster), zelia.level, zelia.spell_book, zelia.memorized_spells)
# True 1 ('sleep',) ()

level instance-attribute

level: int

The caster's level, which sets slots, range, and effect size. A scroll is read at the scroll's own level rather than the reader's.

spell_book instance-attribute

spell_book: tuple[str, ...]

The spell ids an arcane caster owns copies of. Empty for a divine caster.

memorized_spells instance-attribute

memorized_spells: tuple[MemorizedSpell, ...]

The prepared copies as MemorizedSpell entries, spent as they are cast.

Combatant

Bases: Creature, Protocol

A creature with the numbers an attack, an initiative roll, or a saving throw reads.

attack_roll, resolve_attack, saving_throw, and participant_modifier take this. Both concrete creature types satisfy it. Every number is read-only, because each one is derived: a character's from class, level, and gear, a monster's from its template and its drained hit dice.

Examples:

from osrlib.core.creature import Combatant
from osrlib.core.monsters import MONSTER_SPAWN_STREAM, spawn_monster
from osrlib.core.rng import RngStreams
from osrlib.data import load_monsters

stream = RngStreams(master_seed=3).get(MONSTER_SPAWN_STREAM)
goblin = spawn_monster(load_monsters().get("goblin"), id="monster-0001", stream=stream)
print(isinstance(goblin, Combatant), goblin.thac0, goblin.armour_class)
# True 19 6

thac0 property

thac0: int

The descending-AC to-hit number the attack matrix is entered with.

attack_bonus property

attack_bonus: int

The ascending-AC equivalent of thac0.

armour_class property

armour_class: int | None

Descending armour class, or None for a creature an attack roll cannot hit.

armour_class_ascending property

armour_class_ascending: int | None

The ascending form of armour_class, or None on the same terms.

saves property

saves: SavingThrows

The five saving-throw targets as SavingThrows.

melee_modifier property

melee_modifier: int

The bonus a melee attack and its damage take, from strength or from an effect.

missile_modifier property

missile_modifier: int

The bonus a missile attack takes, from dexterity. A monster's is 0.

initiative_modifier property

initiative_modifier: int

The bonus an individual initiative roll takes. A monster's is 0.

Creature

Bases: Protocol

What every rules function needs from the creature it acts on.

A Character and a MonsterInstance both satisfy this, and so does anything of your own with these attributes. Functions that read only this surface, such as has_condition, apply_healing, and incapacitated, take it. A function that also reads combat numbers takes Combatant.

The hit points, conditions, and stat modifiers are writable, because the rules change them: damage lowers current_hp, a spell appends to conditions, an effect attaches a modifier. The id, name, and alignment are read-only here, because a monster's name comes from its template and a character's id is None until a session assigns one.

Examples:

from osrlib.core.creature import Creature
from osrlib.core.monsters import MONSTER_SPAWN_STREAM, spawn_monster
from osrlib.core.rng import RngStreams
from osrlib.data import load_monsters

stream = RngStreams(master_seed=3).get(MONSTER_SPAWN_STREAM)
goblin = spawn_monster(load_monsters().get("goblin"), id="monster-0001", stream=stream)
print(isinstance(goblin, Creature), goblin.name, goblin.current_hp == goblin.max_hp)
# True Goblin True

id property

id: str | None

The entity id a session assigned, or None for a character no session holds yet.

Events name creatures by it.

name property

name: str

The display name: the one a character was created with, or the one a monster's template gives.

alignment property

alignment: Alignment | None

The creature's Alignment.

None for a monster whose template lists none.

current_hp instance-attribute

current_hp: int

Hit points now. The rules lower and raise it in place.

max_hp instance-attribute

max_hp: int

The hit-point ceiling healing cannot pass.

conditions instance-attribute

conditions: tuple[ActiveCondition, ...]

The active ActiveCondition entries, in the order they were granted. Read them through has_condition.

stat_modifiers instance-attribute

stat_modifiers: tuple[ActiveModifier, ...]

The active ActiveModifier entries. Read them through modifier_total and its siblings.