Skip to content

maid-stdlib

The maid-stdlib package provides the standard library of reusable components, systems, events, commands, and utilities for MAID games.

Installation

pip install maid-stdlib

Module Overview

Components (maid_stdlib.components)

Reusable ECS components for common game mechanics:

  • HealthComponent - Hit points and health management
  • ManaComponent - Magic points for spellcasting
  • StaminaComponent - Stamina for physical actions
  • InventoryComponent - Item storage and capacity
  • PositionComponent - Location tracking
  • StatsComponent - Character attributes (STR, DEX, etc.)
  • CombatComponent - Combat state and cooldowns
  • EquipmentComponent - Equipped items
  • ItemComponent - Item properties
  • NPCComponent - NPC behavior and state
  • PlayerComponent - Player-specific data
  • DescriptionComponent - Entity descriptions
  • MovementComponent - Movement tracking

Systems (maid_stdlib.systems)

Base systems implementing common game mechanics:

  • HealthRegenerationSystem - Regenerate health over time
  • ManaRegenerationSystem - Regenerate mana over time
  • StaminaRegenerationSystem - Regenerate stamina over time
  • MovementRegenerationSystem - Regenerate movement points
  • PositionSyncSystem - Sync entity positions
  • HealthCheckSystem - Check for entity death

Events (maid_stdlib.events)

Standard events for common game actions:

  • RoomEnterEvent - Entity enters a room
  • RoomLeaveEvent - Entity leaves a room
  • CombatStartEvent - Combat begins
  • CombatEndEvent - Combat ends
  • DamageDealtEvent - Damage dealt to entity
  • EntityDeathEvent - Entity dies
  • ItemPickedUpEvent - Item picked up
  • ItemDroppedEvent - Item dropped
  • MessageEvent - Message to display

Commands (maid_stdlib.commands)

Standard commands available to all games:

Command Description
look Look at room or object
move / directions Move between rooms
get / take Pick up items
drop Drop items
inventory / i View inventory
help Get command help
language Set language preference

Utilities (maid_stdlib.utils)

Helper functions and utilities:

  • dice - Dice rolling functions
  • formatting - Text formatting and colors
  • editor - In-game text editor
  • menu - Interactive menu system
  • table - ASCII table formatting

Components

HealthComponent

Component for tracking entity health.

HealthComponent

Bases: Component

Tracks entity health.

percentage property

percentage: float

Get health as percentage.

is_alive property

is_alive: bool

Check if entity is alive.

damage

damage(amount: int) -> int

Apply damage, returns actual damage dealt.

heal

heal(amount: int) -> int

Apply healing, returns actual amount healed.

ManaComponent

Component for tracking magical energy.

ManaComponent

Bases: Component

Tracks entity mana/magical energy.

percentage property

percentage: float

Get mana as percentage.

consume

consume(amount: int) -> bool

Consume mana. Returns False if insufficient.

restore

restore(amount: int) -> int

Restore mana, returns actual amount restored.

StaminaComponent

Component for tracking physical energy.

StaminaComponent

Bases: Component

Stamina resource for physical abilities.

percentage property

percentage: float

Get stamina as percentage.

consume

consume(amount: int) -> bool

Consume stamina. Returns False if insufficient.

restore

restore(amount: int) -> int

Restore stamina, returns actual amount restored.

InventoryComponent

Component for item storage.

InventoryComponent

Bases: Component

Tracks entity inventory.

is_full property

is_full: bool

Check if inventory is at capacity.

available_slots property

available_slots: int

Get number of available slots.

available_weight property

available_weight: float

Get available weight capacity.

can_add_item

can_add_item(weight: float = 0.0) -> bool

Check if an item can be added (slot and weight check).

add_item

add_item(item_id: UUID, weight: float = 0.0) -> bool

Add an item to inventory.

Parameters:

Name Type Description Default
item_id UUID

ID of item to add

required
weight float

Weight of the item (default 0.0)

0.0

Returns:

Type Description
bool

False if inventory full or weight limit exceeded

remove_item

remove_item(item_id: UUID, weight: float = 0.0) -> bool

Remove an item from inventory.

Parameters:

Name Type Description Default
item_id UUID

ID of item to remove

required
weight float

Weight of the item (default 0.0)

0.0

Returns:

Type Description
bool

False if item not found

PositionComponent

Component for location tracking.

PositionComponent

Bases: Component

Tracks entity position in the world.

StatsComponent

Component for character attributes.

StatsComponent

Bases: Component

Core RPG statistics for characters.

These are the base attributes that influence all other derived stats. Content packs may use these to calculate combat, skill, and other values.

get_modifier

get_modifier(stat: str) -> int

Get the modifier for a stat (D&D-style: (stat - 10) // 2).

Parameters:

Name Type Description Default
stat str

Stat name (strength, dexterity, etc.)

required

Returns:

Type Description
int

Modifier value (-5 to +10 typically)

add_experience

add_experience(amount: int) -> int

Add experience and return number of levels gained.

Parameters:

Name Type Description Default
amount int

Experience to add

required

Returns:

Type Description
int

Number of levels gained (0 if none)


Systems

HealthRegenerationSystem

System that regenerates entity health over time.

HealthRegenerationSystem

HealthRegenerationSystem(world: World)

Bases: System

Regenerates health for entities not in combat.

Uses HealthComponent.regeneration_rate to determine regen speed. Skips entities that are in combat (have CombatComponent with in_combat=True).

update async

update(delta: float) -> None

Regenerate health for all eligible entities.

shutdown async

shutdown() -> None

Clean up accumulators on system shutdown.

ManaRegenerationSystem

System that regenerates entity mana over time.

ManaRegenerationSystem

ManaRegenerationSystem(world: World)

Bases: System

Regenerates mana for entities.

Uses ManaComponent.regeneration_rate to determine regen speed. Regenerates even in combat (but content packs can override).

update async

update(delta: float) -> None

Regenerate mana for all eligible entities.

shutdown async

shutdown() -> None

Clean up accumulators on system shutdown.

StaminaRegenerationSystem

System that regenerates entity stamina over time.

StaminaRegenerationSystem

StaminaRegenerationSystem(world: World)

Bases: System

Regenerates stamina for entities.

Uses different rates for in-combat vs out-of-combat: - StaminaComponent.regeneration_rate: out of combat - StaminaComponent.combat_regen_rate: in combat

update async

update(delta: float) -> None

Regenerate stamina for all eligible entities.

shutdown async

shutdown() -> None

Clean up accumulators on system shutdown.


Utilities

Dice Utilities

Functions for rolling dice with standard notation.

from maid_stdlib.utils import roll_dice, roll, parse_dice_notation

# Roll 2d6+3
result = roll_dice("2d6+3")
print(f"Rolled {result.total} (rolls: {result.rolls})")

# Quick roll
total = roll(2, 6, 3)  # 2d6+3

# Parse notation
parsed = parse_dice_notation("3d8-2")
print(f"Dice: {parsed.num_dice}d{parsed.sides}{parsed.modifier:+d}")

roll_dice

roll_dice(
    num_dice: int = 1, sides: int = 6, modifier: int = 0
) -> DiceResult

Roll dice and return detailed results.

Parameters:

Name Type Description Default
num_dice int

Number of dice to roll

1
sides int

Number of sides on each die

6
modifier int

Bonus/penalty to add to total

0

Returns:

Type Description
DiceResult

DiceResult with total, individual rolls, and notation

roll

roll(
    num_dice: int = 1, sides: int = 6, modifier: int = 0
) -> int

Roll dice and return the total.

Parameters:

Name Type Description Default
num_dice int

Number of dice to roll

1
sides int

Number of sides on each die

6
modifier int

Bonus/penalty to add to total

0

Returns:

Type Description
int

Total of all dice plus modifier

Text Formatting

Functions for formatting text with colors and styles.

from maid_stdlib.utils import colorize, strip_colors, wrap_text, center_text

# Apply colors
text = colorize("Hello, World!", "bright_green")

# Parse color tags
text = parse_color_tags("{red}Warning:{/red} {yellow}Caution!{/yellow}")

# Wrap long text
wrapped = wrap_text(long_text, width=80)

# Center text
centered = center_text("Title", width=40, fill_char="=")

colorize

colorize(text: str, color: str) -> str

Apply ANSI color to text.

Parameters:

Name Type Description Default
text str

Text to colorize

required
color str

Color name (see COLORS dict)

required

Returns:

Type Description
str

Text with ANSI color codes

strip_colors

strip_colors(text: str) -> str

Remove all ANSI color codes from text.

Parameters:

Name Type Description Default
text str

Text with ANSI codes

required

Returns:

Type Description
str

Plain text without color codes

Table Formatting

Create ASCII tables for displaying data.

from maid_stdlib.utils import format_table

# Simple table
headers = ["Name", "Level", "Class"]
rows = [
    ["Gandalf", "20", "Wizard"],
    ["Aragorn", "15", "Ranger"],
]
table = format_table(headers, rows)
print(table)

format_table

format_table(
    rows: list[list[str]],
    headers: list[str] | None = None,
    column_sep: str = " | ",
) -> str

Format data as a simple text table.

Parameters:

Name Type Description Default
rows list[list[str]]

List of rows, each row is a list of cell values

required
headers list[str] | None

Optional header row

None
column_sep str

Separator between columns

' | '

Returns:

Type Description
str

Formatted table as string


Content Pack

The stdlib provides a content pack that registers all components, systems, and commands:

from maid_engine import GameEngine
from maid_stdlib import StdlibContentPack

engine = GameEngine(settings)
engine.load_content_pack(StdlibContentPack())

StdlibContentPack

Standard library content pack.

Provides common components, base systems, events, and commands that most MUD games will need.

manifest property

Get pack manifest.

get_dependencies

get_dependencies() -> list[str]

Get pack dependencies.

get_systems

get_systems(world: World) -> list[System]

Get systems provided by this pack.

get_events

get_events() -> list[type[Event]]

Get events defined by this pack.

register_commands

register_commands(registry: AnyCommandRegistry) -> None

Register commands provided by this pack.

register_document_schemas

register_document_schemas(store: DocumentStore) -> None

Register document schemas used by this pack.

on_load async

on_load(engine: GameEngine) -> None

Called when pack is loaded.

on_unload async

on_unload(engine: GameEngine) -> None

Called when pack is unloaded.


Package API

Full package API documentation:

maid_stdlib

MAID Standard Library - Common components, systems, and utilities.

This package provides reusable building blocks for MUD games:

  • Common components (Health, Inventory, Position, Stats)
  • Base abstract systems (combat, inventory, movement)
  • Standard events (movement, combat, item interactions)
  • Basic commands (look, move, get, drop, inventory)
  • Utility functions (dice rolling, text formatting)

CombatComponent

Bases: Component

Combat-related attributes.

CombatEndEvent dataclass

CombatEndEvent(combatant_ids: list[UUID], room_id: UUID)

Bases: Event

Emitted when combat ends.

CombatStartEvent dataclass

CombatStartEvent(
    attacker_id: UUID, defender_id: UUID, room_id: UUID
)

Bases: Event

Emitted when combat begins.

DamageDealtEvent dataclass

DamageDealtEvent(
    source_id: UUID | None,
    target_id: UUID,
    damage: int,
    damage_type: str,
    hit_location: str | None = None,
)

Bases: Event

Emitted when damage is dealt.

DescriptionComponent

Bases: Component

Entity description and display information.

The name, short_desc, and long_desc fields support TranslatableText for internationalization. You can set per-locale translations using the @set command with locale suffixes:

@set entity/DescriptionComponent.name.es = "Spanish name"
@set entity/DescriptionComponent.short_desc.de = "German description"

When fields are plain strings, they work normally. When a locale-specific translation is set via @set, the field is automatically converted to TranslatableText to store the translations.

matches_keyword

matches_keyword(keyword: str) -> bool

Check if entity matches a keyword search.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def matches_keyword(self, keyword: str) -> bool:
    """Check if entity matches a keyword search."""
    keyword_lower = keyword.lower()
    # Handle TranslatableText - convert to string for comparison
    name_str = str(self.name)
    if keyword_lower in name_str.lower():
        return True
    return any(keyword_lower in kw.lower() for kw in self.keywords)

DiceResult dataclass

DiceResult(
    total: int,
    rolls: list[int],
    modifier: int,
    notation: str,
)

Result of a dice roll.

EntityDeathEvent dataclass

EntityDeathEvent(
    entity_id: UUID, killer_id: UUID | None = None
)

Bases: Event

Emitted when an entity dies.

EquipmentComponent

Bases: Component

Tracks equipped items by slot.

equip

equip(slot: str, item_id: UUID) -> UUID | None

Equip item to slot, returns previously equipped item.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def equip(self, slot: str, item_id: UUID) -> UUID | None:
    """Equip item to slot, returns previously equipped item."""
    previous = self.slots.get(slot)
    self.slots[slot] = item_id
    return previous

get_equipped

get_equipped(slot: str) -> UUID | None

Get item equipped in slot.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def get_equipped(self, slot: str) -> UUID | None:
    """Get item equipped in slot."""
    return self.slots.get(slot)

unequip

unequip(slot: str) -> UUID | None

Unequip item from slot, returns the item.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def unequip(self, slot: str) -> UUID | None:
    """Unequip item from slot, returns the item."""
    return self.slots.pop(slot, None)

HealthCheckSystem

HealthCheckSystem(world: World)

Bases: System

Monitors health and emits EntityDeathEvent when health reaches 0.

This system runs after damage-dealing systems to detect deaths. It tracks which entities have already had death events emitted to avoid duplicate events.

The "dead" tag is added to entities when they die and should be removed when they are resurrected/respawned.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/health.py
def __init__(self, world: "World") -> None:
    super().__init__(world)
    # Track entities that have died to avoid duplicate events
    # This is cleared when entity is resurrected (loses "dead" tag)
    self._death_emitted: set[str] = set()

shutdown async

shutdown() -> None

Clear tracking state on shutdown.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/health.py
async def shutdown(self) -> None:
    """Clear tracking state on shutdown."""
    self._death_emitted.clear()

update async

update(delta: float) -> None

Check for dead entities and emit death events.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/health.py
async def update(self, delta: float) -> None:  # noqa: ARG002
    """Check for dead entities and emit death events."""
    for entity in self.entities.with_components(HealthComponent):
        entity_key = str(entity.id)
        health = entity.get(HealthComponent)

        if not health.is_alive:
            # Entity is dead
            if not entity.has_tag("dead") and entity_key not in self._death_emitted:
                # First time detecting death - emit event and tag
                entity.add_tag("dead")
                self._death_emitted.add(entity_key)

                await self.events.emit(
                    EntityDeathEvent(
                        entity_id=entity.id,
                        killer_id=None,  # Set by combat system via event data
                    )
                )
        else:
            # Entity is alive
            if entity.has_tag("dead"):
                # Was dead but now alive (resurrected) - remove tag
                entity.remove_tag("dead")
                self._death_emitted.discard(entity_key)
            elif entity_key in self._death_emitted:
                # Clean up tracking if entity was never properly tagged
                self._death_emitted.discard(entity_key)

HealthComponent

Bases: Component

Tracks entity health.

is_alive property

is_alive: bool

Check if entity is alive.

percentage property

percentage: float

Get health as percentage.

damage

damage(amount: int) -> int

Apply damage, returns actual damage dealt.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def damage(self, amount: int) -> int:
    """Apply damage, returns actual damage dealt."""
    actual = min(amount, self.current)
    self.current -= actual
    return actual

heal

heal(amount: int) -> int

Apply healing, returns actual amount healed.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def heal(self, amount: int) -> int:
    """Apply healing, returns actual amount healed."""
    actual = min(amount, self.maximum - self.current)
    self.current += actual
    return actual

HealthRegenerationSystem

HealthRegenerationSystem(world: World)

Bases: System

Regenerates health for entities not in combat.

Uses HealthComponent.regeneration_rate to determine regen speed. Skips entities that are in combat (have CombatComponent with in_combat=True).

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
def __init__(self, world: "World") -> None:
    super().__init__(world)
    # Accumulate fractional regeneration between ticks
    self._accumulators: dict[str, float] = {}

shutdown async

shutdown() -> None

Clean up accumulators on system shutdown.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def shutdown(self) -> None:
    """Clean up accumulators on system shutdown."""
    self._accumulators.clear()

update async

update(delta: float) -> None

Regenerate health for all eligible entities.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def update(self, delta: float) -> None:
    """Regenerate health for all eligible entities."""
    for entity in self.entities.with_components(HealthComponent):
        health = entity.get(HealthComponent)

        # Skip dead entities
        if not health.is_alive:
            continue

        # Skip if already at max
        if health.current >= health.maximum:
            self._accumulators.pop(str(entity.id), None)
            continue

        # Check combat state if entity has CombatComponent
        combat = entity.try_get(CombatComponent)
        if combat and combat.in_combat:
            continue

        # Calculate and apply regeneration
        entity_key = str(entity.id)
        regen = health.regeneration_rate * delta

        # Skip if rate is zero or negative (no regeneration)
        if health.regeneration_rate <= 0:
            continue

        # Accumulate fractional regen
        accumulated = self._accumulators.get(entity_key, 0.0) + regen
        whole_points = int(accumulated)

        if whole_points > 0:
            health.heal(whole_points)
            self._accumulators[entity_key] = accumulated - whole_points
        else:
            self._accumulators[entity_key] = accumulated

InventoryComponent

Bases: Component

Tracks entity inventory.

available_slots property

available_slots: int

Get number of available slots.

available_weight property

available_weight: float

Get available weight capacity.

is_full property

is_full: bool

Check if inventory is at capacity.

add_item

add_item(item_id: UUID, weight: float = 0.0) -> bool

Add an item to inventory.

Parameters:

Name Type Description Default
item_id UUID

ID of item to add

required
weight float

Weight of the item (default 0.0)

0.0

Returns:

Type Description
bool

False if inventory full or weight limit exceeded

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def add_item(self, item_id: UUID, weight: float = 0.0) -> bool:
    """Add an item to inventory.

    Args:
        item_id: ID of item to add
        weight: Weight of the item (default 0.0)

    Returns:
        False if inventory full or weight limit exceeded
    """
    if not self.can_add_item(weight):
        return False
    self.items.append(item_id)
    self.current_weight += weight
    return True

can_add_item

can_add_item(weight: float = 0.0) -> bool

Check if an item can be added (slot and weight check).

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def can_add_item(self, weight: float = 0.0) -> bool:
    """Check if an item can be added (slot and weight check)."""
    if self.is_full:
        return False
    return not (weight > 0 and self.current_weight + weight > self.weight_limit)

remove_item

remove_item(item_id: UUID, weight: float = 0.0) -> bool

Remove an item from inventory.

Parameters:

Name Type Description Default
item_id UUID

ID of item to remove

required
weight float

Weight of the item (default 0.0)

0.0

Returns:

Type Description
bool

False if item not found

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def remove_item(self, item_id: UUID, weight: float = 0.0) -> bool:
    """Remove an item from inventory.

    Args:
        item_id: ID of item to remove
        weight: Weight of the item (default 0.0)

    Returns:
        False if item not found
    """
    if item_id not in self.items:
        return False
    self.items.remove(item_id)
    self.current_weight = max(0.0, self.current_weight - weight)
    return True

ItemComponent

Bases: Component

Marks an entity as an item.

This component identifies item entities and holds item-specific metadata like template, quality, and ownership.

ItemDroppedEvent dataclass

ItemDroppedEvent(
    entity_id: UUID, item_id: UUID, room_id: UUID
)

Bases: Event

Emitted when an item is dropped.

ItemPickedUpEvent dataclass

ItemPickedUpEvent(
    entity_id: UUID, item_id: UUID, room_id: UUID
)

Bases: Event

Emitted when an item is picked up.

ManaComponent

Bases: Component

Tracks entity mana/magical energy.

percentage property

percentage: float

Get mana as percentage.

consume

consume(amount: int) -> bool

Consume mana. Returns False if insufficient.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def consume(self, amount: int) -> bool:
    """Consume mana. Returns False if insufficient."""
    if self.current < amount:
        return False
    self.current -= amount
    return True

restore

restore(amount: int) -> int

Restore mana, returns actual amount restored.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def restore(self, amount: int) -> int:
    """Restore mana, returns actual amount restored."""
    actual = min(amount, self.maximum - self.current)
    self.current += actual
    return actual

ManaRegenerationSystem

ManaRegenerationSystem(world: World)

Bases: System

Regenerates mana for entities.

Uses ManaComponent.regeneration_rate to determine regen speed. Regenerates even in combat (but content packs can override).

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
def __init__(self, world: "World") -> None:
    super().__init__(world)
    self._accumulators: dict[str, float] = {}

shutdown async

shutdown() -> None

Clean up accumulators on system shutdown.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def shutdown(self) -> None:
    """Clean up accumulators on system shutdown."""
    self._accumulators.clear()

update async

update(delta: float) -> None

Regenerate mana for all eligible entities.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def update(self, delta: float) -> None:
    """Regenerate mana for all eligible entities."""
    for entity in self.entities.with_components(ManaComponent):
        mana = entity.get(ManaComponent)

        # Skip if already at max
        if mana.current >= mana.maximum:
            self._accumulators.pop(str(entity.id), None)
            continue

        # Calculate and apply regeneration
        entity_key = str(entity.id)
        regen = mana.regeneration_rate * delta

        # Skip if rate is zero or negative (no regeneration)
        if mana.regeneration_rate <= 0:
            continue

        accumulated = self._accumulators.get(entity_key, 0.0) + regen
        whole_points = int(accumulated)

        if whole_points > 0:
            mana.restore(whole_points)
            self._accumulators[entity_key] = accumulated - whole_points
        else:
            self._accumulators[entity_key] = accumulated

MessageEvent dataclass

MessageEvent(
    sender_id: UUID | None,
    target_ids: list[UUID],
    channel: str,
    message: str,
)

Bases: Event

Emitted for communication messages.

MovementComponent

Bases: Component

Tracks entity movement points and speed.

MovementRegenerationSystem

MovementRegenerationSystem(world: World)

Bases: System

Regenerates movement points for entities.

Uses MovementComponent.regeneration_rate to determine regen speed.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
def __init__(self, world: "World") -> None:
    super().__init__(world)
    self._accumulators: dict[str, float] = {}

shutdown async

shutdown() -> None

Clean up accumulators on system shutdown.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def shutdown(self) -> None:
    """Clean up accumulators on system shutdown."""
    self._accumulators.clear()

update async

update(delta: float) -> None

Regenerate movement for all eligible entities.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def update(self, delta: float) -> None:
    """Regenerate movement for all eligible entities."""
    for entity in self.entities.with_components(MovementComponent):
        movement = entity.get(MovementComponent)

        # Skip if already at max
        if movement.current >= movement.maximum:
            self._accumulators.pop(str(entity.id), None)
            continue

        # Calculate and apply regeneration
        entity_key = str(entity.id)
        regen = movement.regeneration_rate * delta

        # Skip if rate is zero or negative (no regeneration)
        if movement.regeneration_rate <= 0:
            continue

        accumulated = self._accumulators.get(entity_key, 0.0) + regen
        whole_points = int(accumulated)

        if whole_points > 0:
            # MovementComponent doesn't have restore(), update directly
            new_movement = min(movement.maximum, movement.current + whole_points)
            movement.current = new_movement
            self._accumulators[entity_key] = accumulated - whole_points
        else:
            self._accumulators[entity_key] = accumulated

NPCComponent

Bases: Component

Marks an entity as a non-player character.

This component identifies NPC entities and holds NPC-specific metadata like AI behavior, dialogue, and spawn information.

PlayerComponent

Bases: Component

Marks an entity as a player character.

This component identifies player-controlled entities and holds player-specific metadata like account linkage and session info.

PositionComponent

Bases: Component

Tracks entity position in the world.

PositionSyncSystem

PositionSyncSystem(world: World)

Bases: System

Synchronizes PositionComponent with World room index.

This system runs early (low priority number) to ensure the room index is up-to-date before other systems query room occupants.

It handles: - Initial placement of entities in rooms - Detecting position changes and updating the room index - Tracking last known position to detect changes

Source code in packages/maid-stdlib/src/maid_stdlib/systems/position.py
def __init__(self, world: "World") -> None:
    super().__init__(world)
    # Track last known room for each entity to detect changes
    self._last_room: dict[UUID, UUID] = {}

shutdown async

shutdown() -> None

Clean up tracking data.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/position.py
async def shutdown(self) -> None:
    """Clean up tracking data."""
    self._last_room.clear()

update async

update(delta: float) -> None

Sync position components with world room index.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/position.py
async def update(self, delta: float) -> None:  # noqa: ARG002
    """Sync position components with world room index."""
    for entity in self.entities.with_components(PositionComponent):
        pos = entity.get(PositionComponent)
        entity_id = entity.id
        target_room = pos.room_id

        # Get current room from index
        current_room = self.world.get_entity_room(entity_id)

        if current_room is None:
            # Entity not in room index yet - initial placement
            self.world.place_entity_in_room(entity_id, target_room)
            self._last_room[entity_id] = target_room
        elif current_room != target_room:
            # Position changed - update room index
            self.world.move_entity(entity_id, target_room)
            self._last_room[entity_id] = target_room
        else:
            # Already in sync, just update tracking
            self._last_room[entity_id] = target_room

RoomEnterEvent dataclass

RoomEnterEvent(
    entity_id: UUID,
    room_id: UUID,
    from_room_id: UUID | None = None,
)

Bases: Event

Emitted when an entity enters a room.

RoomLeaveEvent dataclass

RoomLeaveEvent(
    entity_id: UUID,
    room_id: UUID,
    to_room_id: UUID | None = None,
)

Bases: Event

Emitted when an entity leaves a room.

StaminaComponent

Bases: Component

Stamina resource for physical abilities.

percentage property

percentage: float

Get stamina as percentage.

consume

consume(amount: int) -> bool

Consume stamina. Returns False if insufficient.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def consume(self, amount: int) -> bool:
    """Consume stamina. Returns False if insufficient."""
    if self.current < amount:
        return False
    self.current -= amount
    return True

restore

restore(amount: int) -> int

Restore stamina, returns actual amount restored.

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def restore(self, amount: int) -> int:
    """Restore stamina, returns actual amount restored."""
    actual = min(amount, self.maximum - self.current)
    self.current += actual
    return actual

StaminaRegenerationSystem

StaminaRegenerationSystem(world: World)

Bases: System

Regenerates stamina for entities.

Uses different rates for in-combat vs out-of-combat: - StaminaComponent.regeneration_rate: out of combat - StaminaComponent.combat_regen_rate: in combat

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
def __init__(self, world: "World") -> None:
    super().__init__(world)
    self._accumulators: dict[str, float] = {}

shutdown async

shutdown() -> None

Clean up accumulators on system shutdown.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def shutdown(self) -> None:
    """Clean up accumulators on system shutdown."""
    self._accumulators.clear()

update async

update(delta: float) -> None

Regenerate stamina for all eligible entities.

Source code in packages/maid-stdlib/src/maid_stdlib/systems/regeneration.py
async def update(self, delta: float) -> None:
    """Regenerate stamina for all eligible entities."""
    for entity in self.entities.with_components(StaminaComponent):
        stamina = entity.get(StaminaComponent)

        # Skip if already at max
        if stamina.current >= stamina.maximum:
            self._accumulators.pop(str(entity.id), None)
            continue

        # Determine regen rate based on combat state
        combat = entity.try_get(CombatComponent)
        if combat and combat.in_combat:
            regen_rate = stamina.combat_regen_rate
        else:
            regen_rate = stamina.regeneration_rate

        # Skip if rate is zero or negative (no regeneration)
        if regen_rate <= 0:
            continue

        # Calculate and apply regeneration
        entity_key = str(entity.id)
        regen = regen_rate * delta

        accumulated = self._accumulators.get(entity_key, 0.0) + regen
        whole_points = int(accumulated)

        if whole_points > 0:
            stamina.restore(whole_points)
            self._accumulators[entity_key] = accumulated - whole_points
        else:
            self._accumulators[entity_key] = accumulated

StatsComponent

Bases: Component

Core RPG statistics for characters.

These are the base attributes that influence all other derived stats. Content packs may use these to calculate combat, skill, and other values.

add_experience

add_experience(amount: int) -> int

Add experience and return number of levels gained.

Parameters:

Name Type Description Default
amount int

Experience to add

required

Returns:

Type Description
int

Number of levels gained (0 if none)

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def add_experience(self, amount: int) -> int:
    """Add experience and return number of levels gained.

    Args:
        amount: Experience to add

    Returns:
        Number of levels gained (0 if none)
    """
    self.experience += amount
    levels_gained = 0
    while self.experience >= self.experience_to_level:
        self.experience -= self.experience_to_level
        self.level += 1
        levels_gained += 1
        # Increase XP required for next level
        self.experience_to_level = int(self.experience_to_level * 1.5)
    return levels_gained

get_modifier

get_modifier(stat: str) -> int

Get the modifier for a stat (D&D-style: (stat - 10) // 2).

Parameters:

Name Type Description Default
stat str

Stat name (strength, dexterity, etc.)

required

Returns:

Type Description
int

Modifier value (-5 to +10 typically)

Source code in packages/maid-stdlib/src/maid_stdlib/components/core.py
def get_modifier(self, stat: str) -> int:
    """Get the modifier for a stat (D&D-style: (stat - 10) // 2).

    Args:
        stat: Stat name (strength, dexterity, etc.)

    Returns:
        Modifier value (-5 to +10 typically)
    """
    value = getattr(self, stat, 10)
    return (value - 10) // 2

StdlibContentPack

Standard library content pack.

Provides common components, base systems, events, and commands that most MUD games will need.

manifest property

Get pack manifest.

get_dependencies

get_dependencies() -> list[str]

Get pack dependencies.

Source code in packages/maid-stdlib/src/maid_stdlib/pack.py
def get_dependencies(self) -> list[str]:
    """Get pack dependencies."""
    return []

get_events

get_events() -> list[type[Event]]

Get events defined by this pack.

Source code in packages/maid-stdlib/src/maid_stdlib/pack.py
def get_events(self) -> list[type[Event]]:
    """Get events defined by this pack."""
    from maid_stdlib.events import get_stdlib_events

    return get_stdlib_events()

get_systems

get_systems(world: World) -> list[System]

Get systems provided by this pack.

Source code in packages/maid-stdlib/src/maid_stdlib/pack.py
def get_systems(self, world: World) -> list[System]:
    """Get systems provided by this pack."""
    from maid_stdlib.systems import get_stdlib_systems

    return get_stdlib_systems(world)

on_load async

on_load(engine: GameEngine) -> None

Called when pack is loaded.

Source code in packages/maid-stdlib/src/maid_stdlib/pack.py
async def on_load(self, engine: GameEngine) -> None:
    """Called when pack is loaded."""
    pass

on_unload async

on_unload(engine: GameEngine) -> None

Called when pack is unloaded.

Source code in packages/maid-stdlib/src/maid_stdlib/pack.py
async def on_unload(self, engine: GameEngine) -> None:
    """Called when pack is unloaded."""
    pass

register_commands

register_commands(registry: AnyCommandRegistry) -> None

Register commands provided by this pack.

Source code in packages/maid-stdlib/src/maid_stdlib/pack.py
def register_commands(self, registry: AnyCommandRegistry) -> None:
    """Register commands provided by this pack."""
    from maid_stdlib.commands import register_building_commands, register_stdlib_commands

    register_stdlib_commands(registry, pack_name="stdlib")
    register_building_commands(registry, pack_name="stdlib")

register_document_schemas

register_document_schemas(store: DocumentStore) -> None

Register document schemas used by this pack.

Source code in packages/maid-stdlib/src/maid_stdlib/pack.py
def register_document_schemas(self, store: DocumentStore) -> None:
    """Register document schemas used by this pack."""
    # Stdlib doesn't define document schemas - content packs do
    pass

center_text

center_text(
    text: str, width: int = 80, fill_char: str = " "
) -> str

Center text within a given width.

Parameters:

Name Type Description Default
text str

Text to center

required
width int

Total width

80
fill_char str

Character to use for padding

' '

Returns:

Type Description
str

Centered text

Source code in packages/maid-stdlib/src/maid_stdlib/utils/text.py
def center_text(text: str, width: int = 80, fill_char: str = " ") -> str:
    """Center text within a given width.

    Args:
        text: Text to center
        width: Total width
        fill_char: Character to use for padding

    Returns:
        Centered text
    """
    return text.center(width, fill_char)

colorize

colorize(text: str, color: str) -> str

Apply ANSI color to text.

Parameters:

Name Type Description Default
text str

Text to colorize

required
color str

Color name (see COLORS dict)

required

Returns:

Type Description
str

Text with ANSI color codes

Source code in packages/maid-stdlib/src/maid_stdlib/utils/text.py
def colorize(text: str, color: str) -> str:
    """Apply ANSI color to text.

    Args:
        text: Text to colorize
        color: Color name (see COLORS dict)

    Returns:
        Text with ANSI color codes
    """
    color_code = COLORS.get(color.lower(), "")
    reset_code = COLORS["reset"]
    return f"{color_code}{text}{reset_code}"

format_table

format_table(
    rows: list[list[str]],
    headers: list[str] | None = None,
    column_sep: str = " | ",
) -> str

Format data as a simple text table.

Parameters:

Name Type Description Default
rows list[list[str]]

List of rows, each row is a list of cell values

required
headers list[str] | None

Optional header row

None
column_sep str

Separator between columns

' | '

Returns:

Type Description
str

Formatted table as string

Source code in packages/maid-stdlib/src/maid_stdlib/utils/text.py
def format_table(
    rows: list[list[str]],
    headers: list[str] | None = None,
    column_sep: str = " | ",
) -> str:
    """Format data as a simple text table.

    Args:
        rows: List of rows, each row is a list of cell values
        headers: Optional header row
        column_sep: Separator between columns

    Returns:
        Formatted table as string
    """
    if not rows:
        return ""

    # Calculate column widths
    all_rows = [headers] + rows if headers else rows
    num_cols = max(len(row) for row in all_rows)

    widths = [0] * num_cols
    for row in all_rows:
        for i, cell in enumerate(row):
            widths[i] = max(widths[i], len(str(cell)))

    # Format rows
    lines = []

    if headers:
        header_line = column_sep.join(
            str(cell).ljust(widths[i]) for i, cell in enumerate(headers)
        )
        lines.append(header_line)
        lines.append("-" * len(header_line))

    for row in rows:
        cells = []
        for i in range(num_cols):
            cell = str(row[i]) if i < len(row) else ""
            cells.append(cell.ljust(widths[i]))
        lines.append(column_sep.join(cells))

    return "\n".join(lines)

get_stdlib_systems

get_stdlib_systems(world: World) -> list[System]

Get all systems provided by stdlib.

Returns systems in priority order for registration. Content packs can disable or extend these systems as needed.

System priorities: - PositionSyncSystem (5): Sync positions with room index - HealthRegenerationSystem (10): Regen health out of combat - ManaRegenerationSystem (15): Regen mana - StaminaRegenerationSystem (20): Regen stamina (combat-aware) - MovementRegenerationSystem (25): Regen movement points - HealthCheckSystem (60): Emit death events when health reaches 0

Source code in packages/maid-stdlib/src/maid_stdlib/systems/__init__.py
def get_stdlib_systems(world: World) -> list[System]:
    """Get all systems provided by stdlib.

    Returns systems in priority order for registration.
    Content packs can disable or extend these systems as needed.

    System priorities:
    - PositionSyncSystem (5): Sync positions with room index
    - HealthRegenerationSystem (10): Regen health out of combat
    - ManaRegenerationSystem (15): Regen mana
    - StaminaRegenerationSystem (20): Regen stamina (combat-aware)
    - MovementRegenerationSystem (25): Regen movement points
    - HealthCheckSystem (60): Emit death events when health reaches 0
    """
    return [
        PositionSyncSystem(world),
        HealthRegenerationSystem(world),
        ManaRegenerationSystem(world),
        StaminaRegenerationSystem(world),
        MovementRegenerationSystem(world),
        HealthCheckSystem(world),
    ]

parse_color_tags

parse_color_tags(text: str) -> str

Convert MXP-style color tags to ANSI codes.

Converts tags like text to ANSI colored text.

Parameters:

Name Type Description Default
text str

Text with color tags

required

Returns:

Type Description
str

Text with ANSI color codes

Source code in packages/maid-stdlib/src/maid_stdlib/utils/text.py
def parse_color_tags(text: str) -> str:
    """Convert MXP-style color tags to ANSI codes.

    Converts tags like <red>text</red> to ANSI colored text.

    Args:
        text: Text with color tags

    Returns:
        Text with ANSI color codes
    """
    def replace_tag(match: re.Match[str]) -> str:
        is_close = match.group(1) == "/"
        color = match.group(2).lower()

        if is_close:
            return COLORS.get("reset", "")
        return COLORS.get(color, "")

    return COLOR_TAG_PATTERN.sub(replace_tag, text)

parse_dice_notation

parse_dice_notation(notation: str) -> DiceResult

Parse dice notation (e.g., "2d6+3") and roll.

Supported formats: - "d6" or "1d6" - single die - "2d6" - multiple dice - "2d6+3" - with positive modifier - "2d6-2" - with negative modifier

Parameters:

Name Type Description Default
notation str

Dice notation string

required

Returns:

Type Description
DiceResult

DiceResult from rolling the specified dice

Raises:

Type Description
ValueError

If notation is invalid

Source code in packages/maid-stdlib/src/maid_stdlib/utils/dice.py
def parse_dice_notation(notation: str) -> DiceResult:
    """Parse dice notation (e.g., "2d6+3") and roll.

    Supported formats:
    - "d6" or "1d6" - single die
    - "2d6" - multiple dice
    - "2d6+3" - with positive modifier
    - "2d6-2" - with negative modifier

    Args:
        notation: Dice notation string

    Returns:
        DiceResult from rolling the specified dice

    Raises:
        ValueError: If notation is invalid
    """
    # Pattern: optional count, d, sides, optional modifier
    pattern = r"^(\d*)d(\d+)([+-]\d+)?$"
    match = re.match(pattern, notation.strip().lower())

    if not match:
        raise ValueError(f"Invalid dice notation: {notation}")

    count_str, sides_str, mod_str = match.groups()

    num_dice = int(count_str) if count_str else 1
    sides = int(sides_str)
    modifier = int(mod_str) if mod_str else 0

    if num_dice < 1:
        raise ValueError("Must roll at least 1 die")
    if sides < 2:
        raise ValueError("Dice must have at least 2 sides")

    return roll_dice(num_dice, sides, modifier)

roll

roll(
    num_dice: int = 1, sides: int = 6, modifier: int = 0
) -> int

Roll dice and return the total.

Parameters:

Name Type Description Default
num_dice int

Number of dice to roll

1
sides int

Number of sides on each die

6
modifier int

Bonus/penalty to add to total

0

Returns:

Type Description
int

Total of all dice plus modifier

Source code in packages/maid-stdlib/src/maid_stdlib/utils/dice.py
def roll(num_dice: int = 1, sides: int = 6, modifier: int = 0) -> int:
    """Roll dice and return the total.

    Args:
        num_dice: Number of dice to roll
        sides: Number of sides on each die
        modifier: Bonus/penalty to add to total

    Returns:
        Total of all dice plus modifier
    """
    total = sum(random.randint(1, sides) for _ in range(num_dice))
    return total + modifier

roll_dice

roll_dice(
    num_dice: int = 1, sides: int = 6, modifier: int = 0
) -> DiceResult

Roll dice and return detailed results.

Parameters:

Name Type Description Default
num_dice int

Number of dice to roll

1
sides int

Number of sides on each die

6
modifier int

Bonus/penalty to add to total

0

Returns:

Type Description
DiceResult

DiceResult with total, individual rolls, and notation

Source code in packages/maid-stdlib/src/maid_stdlib/utils/dice.py
def roll_dice(num_dice: int = 1, sides: int = 6, modifier: int = 0) -> DiceResult:
    """Roll dice and return detailed results.

    Args:
        num_dice: Number of dice to roll
        sides: Number of sides on each die
        modifier: Bonus/penalty to add to total

    Returns:
        DiceResult with total, individual rolls, and notation
    """
    rolls = [random.randint(1, sides) for _ in range(num_dice)]
    total = sum(rolls) + modifier

    if modifier:
        sign = "+" if modifier > 0 else ""
        notation = f"{num_dice}d{sides}{sign}{modifier}"
    else:
        notation = f"{num_dice}d{sides}"

    return DiceResult(
        total=total,
        rolls=rolls,
        modifier=modifier,
        notation=notation,
    )

strip_colors

strip_colors(text: str) -> str

Remove all ANSI color codes from text.

Parameters:

Name Type Description Default
text str

Text with ANSI codes

required

Returns:

Type Description
str

Plain text without color codes

Source code in packages/maid-stdlib/src/maid_stdlib/utils/text.py
def strip_colors(text: str) -> str:
    """Remove all ANSI color codes from text.

    Args:
        text: Text with ANSI codes

    Returns:
        Plain text without color codes
    """
    ansi_pattern = re.compile(r"\033\[[0-9;]*m")
    return ansi_pattern.sub("", text)

wrap_text

wrap_text(
    text: str,
    width: int = 80,
    indent: str = "",
    subsequent_indent: str = "",
) -> str

Wrap text to specified width.

Parameters:

Name Type Description Default
text str

Text to wrap

required
width int

Maximum line width

80
indent str

Indent for first line

''
subsequent_indent str

Indent for subsequent lines

''

Returns:

Type Description
str

Wrapped text

Source code in packages/maid-stdlib/src/maid_stdlib/utils/text.py
def wrap_text(
    text: str,
    width: int = 80,
    indent: str = "",
    subsequent_indent: str = "",
) -> str:
    """Wrap text to specified width.

    Args:
        text: Text to wrap
        width: Maximum line width
        indent: Indent for first line
        subsequent_indent: Indent for subsequent lines

    Returns:
        Wrapped text
    """
    wrapper = textwrap.TextWrapper(
        width=width,
        initial_indent=indent,
        subsequent_indent=subsequent_indent,
        break_long_words=True,
        break_on_hyphens=True,
    )
    return wrapper.fill(text)