Skip to content

osrlib.crawl.party

The crawl party: marching order, group movement, and combat ranks.

You build a Party out of the Characters your game rolled up, and you hand it to GameSession.new beside the adventure. From then on the session owns it: the party moves as one body, and session.party is where you read it back.

The member list order is marching order. There is no second field to keep in step with it, so the first member is the one in front. ReorderParty is the only command that rewrites that order, and reorder is what it calls.

Dead members stay in the list. Their gear is still on them, so removing them would lose it, and deciding a corpse is left behind is a game's call to make with the referee commands rather than something the rules do for you. Nothing that counts bodies counts a dead one: movement rate, combat ranks, ability checks, and provisions all read living_members.

Party

Bases: BaseModel

The adventuring party, in marching order.

Construct one with at least one member and pass it to GameSession.new, which assigns each member an entity id and keeps the party for the life of the session. The methods here answer what the crawl procedures need to know about the group as a whole: who is still standing, how fast the group walks, and who stands where in a fight.

A party is mutable and validates on assignment, so the session can heal, wound, and re-equip its members in place. The list is never empty: a party whose last member dies ends the session in game_over rather than emptying out.

Attributes:

Name Type Description
members list[Character]

The characters, front of the line first.

Examples:

from osrlib.core.abilities import AbilityScore
from osrlib.core.alignment import Alignment
from osrlib.core.character import Character
from osrlib.crawl.party import Party

rolled = {
    "name": "Hild",
    "class_id": "fighter",
    "race": "human",
    "level": 1,
    "xp": 0,
    "scores": {ability: 12 for ability in AbilityScore},
    "alignment": Alignment.LAWFUL,
    "max_hp": 8,
    "current_hp": 8,
}
party = Party(members=[Character(**rolled), Character(**{**rolled, "name": "Osric"})])
print([member.name for member in party.living_members()])
# ['Hild', 'Osric']

members class-attribute instance-attribute

members: list[Character] = Field(min_length=1)

The party's characters, in marching order: index 0 walks in front and meets what the party walks into first. At least one member is required. Ids are assigned by GameSession.new when the party joins a session, so a party you just built has None in every Character.id until then. Change the order with the ReorderParty command rather than by assigning here, so the change is logged and replays.

living_members

living_members() -> list[Character]

Return the living members, in marching order.

A member is living until something gives them the dead condition. This is the list every group rule works from, so a fallen member stops counting toward movement, ranks, and checks the moment they drop, without leaving the party.

Returns:

Type Description
list[Character]

The members without the dead condition, in marching order. Empty when the whole party has fallen, which is the session's game_over condition.

member

member(character_id: str) -> Character

Return the member with character_id.

Use this to turn an id out of a command or an event back into the character it names. Ids come from GameSession.new, which stamps each member as character-NNNN in party order, and events carry them rather than names.

Parameters:

Name Type Description Default
character_id str

The member's entity id.

required

Returns:

Type Description
Character

The character. Dead members answer here too, because their gear and their record are still the party's.

Raises:

Type Description
ValueError

If no member has that id. The message names the id.

movement_rate

movement_rate(ruleset: Ruleset) -> int

Return the party's exploration rate: the slowest living member's.

B/X moves a group at the pace of its slowest member, so one overloaded character slows everybody. Call Character.movement_rate for one character's own allowance, and exploration_rate for the rate the running session charges the party, which computes the same minimum and halves a member's rate first when hunger or thirst has caught up with them under the deprivation_penalties ruleset flag.

Parameters:

Name Type Description Default
ruleset Ruleset

The ruleset whose encumbrance mode governs. Encumbrance is what turns carried weight into a rate, and the modes differ in what they weigh.

required

Returns:

Type Description
int

The rate in feet per exploration turn: 120, 90, 60, 30, or 0. A rate of 0 means the party cannot move at all, either because its slowest living member is overloaded or because nobody is alive to walk.

ranks

ranks(width: int) -> list[list[Character]]

Chunk the living members into combat ranks of width, in marching order.

A rank is one row of the formation: the first width living members stand in front and take the melee, the rest queue behind them. Battle calls this for you with the width it measured from the space the party is standing in, so you rarely pass your own. Call it yourself to draw the formation, or to answer "who is in front" outside a fight.

Ranks are derived on every read rather than stored, so the fallen collapse forward on their own: when a front-rank member dies, the next living member is in front from the next read on.

Parameters:

Name Type Description Default
width int

How many characters stand abreast. Battle derives this from the party's frontage (see FIGHTER_FRONTAGE_FEET) while the formation_width_limit ruleset flag is on, and puts the whole party in one rank when it is off.

required

Returns:

Type Description
list[list[Character]]

The ranks, front first. The last rank holds the remainder and can be shorter than width. Empty when nobody is alive.

Raises:

Type Description
ValueError

If width is not positive.

reorder

reorder(character_ids: Sequence[str]) -> None

Rewrite the marching order in place.

This is what ReorderParty calls once it has accepted the command. Issue that command through GameSession.execute rather than calling this yourself, so the change is logged and a replay reproduces it.

Parameters:

Name Type Description Default
character_ids Sequence[str]

Every member's id, in the new order. It has to be a permutation of the current membership: no id may be added, dropped, or repeated. Dead members are named here like anyone else, since they are still in the party.

required

Raises:

Type Description
ValueError

If the ids are not exactly the current membership, or if any member has no id yet (a party that has not joined a session).