Skip to content

Components Reference

This reference documents all built-in components in MAID. Components are pure data containers attached to entities.

Component Base Class

All components inherit from Component:

from maid_engine.core.ecs import Component
from pydantic import ConfigDict

class Component(BaseModel):
    """Base class for all ECS components."""

    model_config = ConfigDict(
        validate_assignment=True,
        use_enum_values=True,
        extra="forbid",
    )

    component_type: ClassVar[str] = ""  # Auto-set to class name

    # Private attributes for persistence change tracking
    _dirty_callback: Callable[[UUID], None] | None  # Called when fields change
    _owner_id: UUID | None                           # Entity that owns this component
    _suppress_dirty: bool                            # Temporarily suppress dirty notifications

    @classmethod
    def get_type(cls) -> str:
        """Get the component type identifier."""
        return cls.component_type or cls.__name__

    def notify_mutation(self) -> None:
        """Mark the owning entity dirty after in-place container mutation."""

When a public field is set via attribute assignment, the component automatically invokes _dirty_callback with the _owner_id, marking the entity as dirty for the persistence system. For in-place mutations (e.g., appending to a list), call notify_mutation() explicitly.

Position and Movement

PositionComponent

Tracks entity position in the world.

class PositionComponent(Component):
    room_id: UUID     # Current room
    x: int = 0        # X coordinate within room
    y: int = 0        # Y coordinate within room
    z: int = 0        # Z coordinate (elevation)

Usage:

from maid_stdlib.components import PositionComponent

# Add to entity
entity.add(PositionComponent(room_id=starting_room_id))

# Get position
pos = entity.get(PositionComponent)
print(f"In room {pos.room_id} at ({pos.x}, {pos.y}, {pos.z})")

# Move within room
pos.x += 1
pos.y += 2

MovementComponent

Tracks movement points and speed.

class MovementComponent(Component):
    current: int              # Current movement points
    maximum: int              # Maximum movement points
    speed: float = 1.0        # Speed multiplier
    regeneration_rate: float = 2.0  # Points per second

Usage:

from maid_stdlib.components import MovementComponent

entity.add(MovementComponent(current=100, maximum=100))

# Check movement
mov = entity.get(MovementComponent)
if mov.current >= cost:
    mov.current -= cost
    # Allow movement

Health and Resources

HealthComponent

Tracks entity health.

class HealthComponent(Component):
    current: int              # Current health
    maximum: int              # Maximum health
    regeneration_rate: float = 1.0  # HP per second

    @property
    def percentage(self) -> float:
        """Health as percentage (0-100)."""

    @property
    def is_alive(self) -> bool:
        """Whether entity is alive."""

    def damage(self, amount: int) -> int:
        """Apply damage, returns actual damage dealt."""

    def heal(self, amount: int) -> int:
        """Apply healing, returns actual amount healed."""

Usage:

from maid_stdlib.components import HealthComponent

entity.add(HealthComponent(current=100, maximum=100))

health = entity.get(HealthComponent)

# Check status
print(f"HP: {health.current}/{health.maximum} ({health.percentage:.0f}%)")
print(f"Alive: {health.is_alive}")

# Apply damage
actual_damage = health.damage(30)
print(f"Dealt {actual_damage} damage")

# Apply healing
actual_healing = health.heal(20)
print(f"Healed {actual_healing} HP")

ManaComponent

Tracks magical energy.

class ManaComponent(Component):
    current: int              # Current mana
    maximum: int              # Maximum mana
    regeneration_rate: float = 0.5  # MP per second

    @property
    def percentage(self) -> float:
        """Mana as percentage."""

    def consume(self, amount: int) -> bool:
        """Consume mana. Returns False if insufficient."""

    def restore(self, amount: int) -> int:
        """Restore mana, returns actual amount restored."""

Usage:

from maid_stdlib.components import ManaComponent

entity.add(ManaComponent(current=50, maximum=100))

mana = entity.get(ManaComponent)

# Cast spell
spell_cost = 20
if mana.consume(spell_cost):
    # Spell succeeds
    cast_spell()
else:
    # Not enough mana
    print("Insufficient mana!")

# Restore mana
restored = mana.restore(30)

StaminaComponent

Tracks physical energy for abilities.

class StaminaComponent(Component):
    current: int = 100
    maximum: int = 100
    regeneration_rate: float = 5.0     # Per second out of combat
    combat_regen_rate: float = 1.0     # Per second in combat

    @property
    def percentage(self) -> float:
        """Stamina as percentage."""

    def consume(self, amount: int) -> bool:
        """Consume stamina. Returns False if insufficient."""

    def restore(self, amount: int) -> int:
        """Restore stamina, returns actual amount restored."""

Combat

CombatComponent

Combat-related attributes.

class CombatComponent(Component):
    attack_power: int = 10         # Base damage
    defense: int = 5               # Damage reduction
    speed: int = 10                # Initiative/action speed
    accuracy: int = 80             # Base hit chance %
    evasion: int = 10              # Dodge chance %
    critical_chance: int = 5       # Critical hit %
    critical_multiplier: float = 2.0
    in_combat: bool = False
    target_id: UUID | None = None

Usage:

from maid_stdlib.components import CombatComponent

entity.add(CombatComponent(
    attack_power=15,
    defense=8,
    critical_chance=10,
))

combat = entity.get(CombatComponent)

# Calculate damage
base_damage = combat.attack_power
if random.randint(1, 100) <= combat.critical_chance:
    damage = int(base_damage * combat.critical_multiplier)
else:
    damage = base_damage

StatsComponent

Core RPG statistics.

class StatsComponent(Component):
    # Primary stats
    strength: int = 10       # Physical power, melee damage
    dexterity: int = 10      # Agility, accuracy, evasion
    constitution: int = 10   # Health, stamina, resistance
    intelligence: int = 10   # Magic power, mana pool
    wisdom: int = 10         # Magic defense, mana regen
    charisma: int = 10       # Social interactions, prices

    # Progression
    level: int = 1
    experience: int = 0
    experience_to_level: int = 100

    def get_modifier(self, stat: str) -> int:
        """Get D&D-style modifier: (stat - 10) // 2."""

    def add_experience(self, amount: int) -> int:
        """Add XP and return levels gained."""

Usage:

from maid_stdlib.components import StatsComponent

entity.add(StatsComponent(
    strength=14,
    dexterity=12,
    constitution=13,
))

stats = entity.get(StatsComponent)

# Get modifier for rolls
str_mod = stats.get_modifier("strength")  # +2 for 14 STR
print(f"Strength modifier: {str_mod:+d}")

# Award experience
levels = stats.add_experience(150)
if levels > 0:
    print(f"Level up! Now level {stats.level}")

Inventory

InventoryComponent

Tracks entity inventory.

class InventoryComponent(Component):
    items: list[UUID] = []        # Item entity IDs
    capacity: int = 20            # Max item slots
    weight_limit: float = 100.0   # Max carry weight
    current_weight: float = 0.0   # Current carry weight

    @property
    def is_full(self) -> bool:
        """Check if at capacity."""

    @property
    def available_slots(self) -> int:
        """Get number of available slots."""

    @property
    def available_weight(self) -> float:
        """Get available weight capacity."""

    def can_add_item(self, weight: float = 0.0) -> bool:
        """Check if item can be added."""

    def add_item(self, item_id: UUID, weight: float = 0.0) -> bool:
        """Add item. Returns False if full or overweight."""

    def remove_item(self, item_id: UUID, weight: float = 0.0) -> bool:
        """Remove item. Returns False if not found."""

Usage:

from maid_stdlib.components import InventoryComponent

entity.add(InventoryComponent(capacity=30, weight_limit=150.0))

inv = entity.get(InventoryComponent)

# Add item
item_weight = 5.0
if inv.add_item(item_id, weight=item_weight):
    print("Item added to inventory")
else:
    print("Inventory full or too heavy!")

# Check status
print(f"Slots: {len(inv.items)}/{inv.capacity}")
print(f"Weight: {inv.current_weight}/{inv.weight_limit}")

EquipmentComponent

Tracks equipped items by slot.

class EquipmentComponent(Component):
    slots: dict[str, UUID | None] = {}  # slot_name -> item_id

    def get_equipped(self, slot: str) -> UUID | None:
        """Get item in slot."""

    def equip(self, slot: str, item_id: UUID) -> UUID | None:
        """Equip item, returns previous item."""

    def unequip(self, slot: str) -> UUID | None:
        """Unequip item, returns the item."""

Usage:

from maid_stdlib.components import EquipmentComponent

entity.add(EquipmentComponent())

equip = entity.get(EquipmentComponent)

# Equip weapon
old_weapon = equip.equip("main_hand", sword_id)
if old_weapon:
    # Put old weapon in inventory
    inventory.add_item(old_weapon)

# Check equipment
weapon = equip.get_equipped("main_hand")
armor = equip.get_equipped("chest")

Description and Display

DescriptionComponent

Entity description and display information.

class DescriptionComponent(Component):
    name: str | TranslatableText          # Display name
    short_desc: str | TranslatableText = ""   # Brief description
    long_desc: str | TranslatableText = ""    # Detailed description
    keywords: list[str] = []              # Search keywords

    def matches_keyword(self, keyword: str) -> bool:
        """Check if entity matches a keyword search."""

Usage:

from maid_stdlib.components import DescriptionComponent

entity.add(DescriptionComponent(
    name="Iron Sword",
    short_desc="A sturdy iron sword",
    long_desc="This well-crafted iron sword bears the mark of the local blacksmith.",
    keywords=["sword", "weapon", "iron"],
))

desc = entity.get(DescriptionComponent)

# Display
print(desc.name)
print(desc.short_desc)

# Search
if desc.matches_keyword("sword"):
    print("Found the sword!")

Entity Types

PlayerComponent

Marks an entity as a player character.

class PlayerComponent(Component):
    account_id: UUID | None = None      # Linked account
    session_id: str | None = None       # Current session
    character_class: str = "adventurer"
    race: str = "human"
    title: str = ""
    is_online: bool = False
    last_login: float | None = None
    play_time: float = 0.0              # Total seconds played

Usage:

from maid_stdlib.components import PlayerComponent

entity.add(PlayerComponent(
    account_id=account.id,
    character_class="warrior",
    race="dwarf",
))

player = entity.get(PlayerComponent)

# Track login
import time
player.is_online = True
player.last_login = time.time()

# Update play time
player.play_time += session_duration

NPCComponent

Marks an entity as a non-player character.

class NPCComponent(Component):
    template_id: UUID | None = None     # NPC template
    behavior_type: str = "passive"      # passive, hostile, friendly, merchant
    dialogue_id: str | None = None      # Dialogue script
    spawn_point_id: UUID | None = None
    respawn_time: float = 300.0         # Seconds until respawn
    wander_radius: int = 0              # Wander distance from spawn
    faction_id: str | None = None
    is_merchant: bool = False
    is_quest_giver: bool = False

Usage:

from maid_stdlib.components import NPCComponent

entity.add(NPCComponent(
    behavior_type="merchant",
    is_merchant=True,
    faction_id="town_guard",
))

npc = entity.get(NPCComponent)

if npc.is_merchant:
    # Show shop interface
    open_shop(entity)

ItemComponent

Marks an entity as an item.

class ItemComponent(Component):
    template_id: UUID | None = None
    item_type: str = "misc"         # weapon, armor, consumable, etc.
    quality: str = "common"         # common, uncommon, rare, epic, legendary
    weight: float = 0.0
    value: int = 0                  # Base gold value
    stack_count: int = 1
    max_stack: int = 1
    is_bound: bool = False          # Cannot be traded/dropped
    owner_id: UUID | None = None
    durability: int | None = None
    max_durability: int | None = None

Usage:

from maid_stdlib.components import ItemComponent

entity.add(ItemComponent(
    item_type="weapon",
    quality="rare",
    weight=3.5,
    value=500,
    durability=100,
    max_durability=100,
))

item = entity.get(ItemComponent)

# Check quality color
quality_colors = {
    "common": "white",
    "uncommon": "green",
    "rare": "blue",
    "epic": "purple",
    "legendary": "orange",
}
color = quality_colors.get(item.quality, "white")

Rooms and Exits

ExitInfo

Metadata for a single room exit (door/lock/hidden state). This is a Pydantic model, not a Component.

class ExitInfo(BaseModel):
    door: bool = False            # Whether exit has a door
    locked: bool = False          # Whether door is locked
    hidden: bool = False          # Whether exit is hidden
    door_open: bool = False       # Whether door is open
    key_id: str | None = None     # Key entity ID for locked doors

ExitMetadataComponent

Persists door/lock/hidden metadata for room exits. Keys are direction names.

class ExitMetadataComponent(Component):
    exits: dict[str, ExitInfo] = {}   # direction -> exit metadata

Usage:

from maid_stdlib.components import ExitMetadataComponent, ExitInfo

entity.add(ExitMetadataComponent(exits={
    "north": ExitInfo(door=True, locked=True, key_id="abc-123"),
    "east": ExitInfo(hidden=True),
}))

meta = entity.get(ExitMetadataComponent)
north = meta.exits.get("north")
if north and north.locked:
    print(f"The door to the north is locked (key: {north.key_id})")

ExtendedRoomComponent

Dynamic room descriptions that change based on time of day, season, weather, and other contextual factors. Supports caching for performance.

class ExtendedRoomComponent(Component):
    descriptions: ExtendedDescriptions = ExtendedDescriptions()
    exit_descriptions: dict[str, ExtendedExitDescription] = {}

    def get_description(self, context: dict[str, Any], use_cache: bool = True) -> str:
        """Get room description for the given context."""

    def get_exit_description(
        self,
        direction: str,
        state: ExitState = ExitState.OPEN,
        context: dict[str, Any] | None = None,
    ) -> str | None:
        """Get description for a specific exit."""

Usage:

from maid_stdlib.components import ExtendedRoomComponent
from maid_stdlib.components.extended_room import (
    ExtendedDescriptions, ExtendedExitDescription, TimeOfDay, Weather,
)

room_desc = ExtendedDescriptions(
    base_description="A cozy tavern.",
    time_variants={TimeOfDay.NIGHT: "The tavern is quiet."},
    weather_effects={Weather.RAIN: "Rain drums on the roof."},
)
entity.add(ExtendedRoomComponent(
    descriptions=room_desc,
    exit_descriptions={"north": ExtendedExitDescription(
        direction="north",
        base_description="A sturdy oak door leads north.",
    )},
))

comp = entity.get(ExtendedRoomComponent)
context = {"time": TimeOfDay.NIGHT, "weather": Weather.RAIN}
description = comp.get_description(context)

Metadata

MetadataComponent

Tracks entity metadata including creation and modification history.

class MetadataComponent(Component):
    created_at: datetime
    created_by: str | None = None
    last_modified_at: datetime | None = None
    last_modified_by: str | None = None
    modification_history: list[ModificationRecord] = []
    max_history_entries: int = 100

    def record_modification(
        self,
        field_name: str,
        modified_by: str | None = None,
        component_name: str | None = None,
        old_value: Any = None,
        new_value: Any = None,
        description: str = "",
    ) -> None:
        """Record a modification."""

    def record_creation(self, created_by: str | None = None) -> None:
        """Record that entity was created."""

    def get_history_display(self, limit: int = 20) -> list[str]:
        """Get formatted history for display."""

Usage:

from maid_stdlib.components import MetadataComponent

entity.add(MetadataComponent())

meta = entity.get(MetadataComponent)
meta.record_creation(created_by="system")

# Record change
meta.record_modification(
    field_name="level",
    modified_by="admin",
    component_name="StatsComponent",
    old_value=5,
    new_value=10,
    description="Admin level boost",
)

# View history
for line in meta.get_history_display():
    print(line)

AI Dialogue

DialogueComponent

AI-powered dialogue configuration for NPCs.

class DialogueComponent(Component):
    # AI configuration
    ai_enabled: bool = True
    provider_name: str | None = None      # None = default provider
    model_name: str | None = None         # None = provider default

    # Personality
    personality: str = ""                 # Personality description
    speaking_style: str = ""              # How they speak
    knowledge_domains: list[str] = []     # What they know about
    secret_knowledge: list[str] = []      # Hidden knowledge

    # Role
    npc_role: str = ""                    # Their job/role
    faction: str | None = None

    # Constraints
    will_discuss: list[str] = []          # Topics they'll discuss
    wont_discuss: list[str] = []          # Topics they avoid

    # Response settings
    max_response_tokens: int = 150
    temperature: float = 0.7

    # Fallbacks
    greeting: str = "Hello there."
    farewell: str = "Goodbye."
    fallback_response: str = "I have nothing to say about that."

    # Rate limiting
    cooldown_seconds: float = 2.0
    last_response_time: float = 0.0

Usage:

from maid_stdlib.components import DialogueComponent

entity.add(DialogueComponent(
    personality="A gruff but kind-hearted dwarf blacksmith",
    speaking_style="Uses mining metaphors and has a thick accent",
    npc_role="blacksmith",
    knowledge_domains=["weapons", "armor", "mining", "metalwork"],
    will_discuss=["smithing", "local gossip", "the mines"],
    wont_discuss=["personal history", "family"],
))

dialogue = entity.get(DialogueComponent)

# Generate response
if dialogue.ai_enabled:
    response = await generate_ai_response(dialogue, player_message)
else:
    response = dialogue.fallback_response

Classic RPG Components

These components are provided by the maid-classic-rpg content pack and extend the stdlib components with classic RPG features.

CharacterStatsComponent

D&D-style character base statistics. Stats range from 1–100 with 10 being average for a human.

class CharacterStatsComponent(Component):
    strength: int = 10
    dexterity: int = 10
    constitution: int = 10
    intelligence: int = 10
    wisdom: int = 10
    charisma: int = 10

    def get_modifier(self, stat: str) -> int:
        """Get D&D-style modifier: (stat - 10) // 2."""

Usage:

from maid_classic_rpg.components import CharacterStatsComponent

entity.add(CharacterStatsComponent(strength=14, dexterity=12))

stats = entity.get(CharacterStatsComponent)
str_mod = stats.get_modifier("strength")  # +2 for 14 STR

CharacterInfoComponent

Character identity and progression information.

class CharacterInfoComponent(Component):
    name: str
    race: str                          # e.g., "human", "elf", "dwarf"
    character_class: str               # e.g., "warrior", "mage"
    level: int = 1
    experience: int = 0
    experience_to_level: int = 1000

    @property
    def level_progress(self) -> float:
        """Get progress toward next level as percentage."""

    def add_experience(self, amount: int) -> int:
        """Add XP and return number of levels gained."""

Usage:

from maid_classic_rpg.components import CharacterInfoComponent

entity.add(CharacterInfoComponent(
    name="Thorin", race="dwarf", character_class="warrior",
))

info = entity.get(CharacterInfoComponent)
levels = info.add_experience(1500)
if levels > 0:
    print(f"Level up! Now level {info.level}")

CharacterFlagsComponent

Boolean flags tracking various character states that affect gameplay.

class CharacterFlagsComponent(Component):
    is_npc: bool = False
    is_immortal: bool = False
    is_invisible: bool = False
    is_hidden: bool = False
    is_resting: bool = False
    is_sleeping: bool = False
    is_flying: bool = False
    is_swimming: bool = False

    @property
    def can_act(self) -> bool:
        """Check if character can perform actions (not sleeping)."""

    @property
    def is_visible(self) -> bool:
        """Check if character is visible to others."""

RestStateComponent

Tracks rest state for regeneration bonuses.

class RestStateComponent(Component):
    state: str = "standing"          # "standing", "resting", or "sleeping"
    regen_multiplier: float = 1.0    # Multiplier for regeneration rates

    @property
    def is_standing(self) -> bool: ...
    @property
    def is_resting(self) -> bool: ...
    @property
    def is_sleeping(self) -> bool: ...

    def stand_up(self) -> bool:
        """Stand up from resting. Returns True if state changed."""
    def start_resting(self) -> bool:
        """Start resting (sit down). Regen multiplier: 2.5x."""
    def start_sleeping(self) -> bool:
        """Start sleeping. Regen multiplier: 5.0x."""
    def wake_up(self) -> bool:
        """Wake up. Returns True if state changed."""

GoldComponent

Currency tracking for characters.

class GoldComponent(Component):
    gold: int = 0                    # Carried gold
    bank_gold: int = 0               # Banked gold

    @property
    def total_gold(self) -> int:
        """Get total gold (carried + banked)."""

    def deposit(self, amount: int) -> bool:
        """Deposit gold to bank. Returns False if insufficient."""
    def withdraw(self, amount: int) -> bool:
        """Withdraw gold from bank. Returns False if insufficient."""
    def add_gold(self, amount: int) -> None:
        """Add gold to carried amount."""
    def remove_gold(self, amount: int) -> bool:
        """Remove carried gold. Returns False if insufficient."""

Usage:

from maid_classic_rpg.components import GoldComponent

entity.add(GoldComponent(gold=100))

gold = entity.get(GoldComponent)
gold.deposit(50)  # 50 carried, 50 banked
print(f"Total: {gold.total_gold}")  # 100

CorpseComponent

Marks an entity as a corpse. Created when entities die, containing inventory and gold. Decays after a configurable time.

class CorpseComponent(Component):
    original_entity_id: UUID
    original_entity_name: str
    is_player_corpse: bool = False
    killer_id: UUID | None = None
    created_at: datetime
    decay_at: datetime
    decay_time_seconds: float = 300.0    # 5 minutes for NPCs
    inventory: list[UUID] = []
    gold: int = 0

    @property
    def is_decayed(self) -> bool:
        """Check if corpse has decayed."""
    @property
    def time_until_decay(self) -> float:
        """Get seconds until corpse decays."""

    def loot_item(self, item_id: UUID) -> bool:
        """Remove an item from the corpse's inventory."""
    def loot_gold(self, amount: int | None = None) -> int:
        """Loot gold (None for all). Returns amount looted."""

GhostComponent

Marks a player entity as being in ghost form after death. Ghost players can move but cannot interact with the living.

class GhostComponent(Component):
    corpse_id: UUID                          # Associated corpse
    death_room_id: UUID                      # Room where death occurred
    died_at: datetime
    resurrection_point_id: UUID | None = None  # Bound resurrection point
    xp_lost: int = 0
    respawn_available_at: datetime | None = None

    @property
    def can_respawn(self) -> bool:
        """Check if ghost can respawn."""
    @property
    def respawn_cooldown(self) -> float:
        """Get seconds until respawn is available."""

NPC Autonomy

These components are defined in maid_stdlib.models.npc.autonomy and support the NPC living-world autonomy system.

NeedsComponent

Tracks NPC needs, mood, and stress for autonomous behavior.

class NeedsComponent(Component):
    needs: dict[NeedCategory, Need] = {}           # Active needs by category
    personality_weights: dict[NeedCategory, float] = {}  # Per-need priority weights
    mood: float = 0.5                              # Current mood (0.0–1.0)
    stress: float = 0.0                            # Current stress level (0.0+)

NeedCategory values: SURVIVAL, ECONOMIC, PURPOSE, COMFORT.

Each Need tracks: category, value, decay_rate, last_satisfied.

Usage:

from maid_stdlib.models.npc.autonomy import NeedsComponent, NeedCategory

entity.add(NeedsComponent(mood=0.7, stress=0.1))

needs = entity.get(NeedsComponent)
print(f"Mood: {needs.mood}, Stress: {needs.stress}")

GoalsComponent

Tracks active, completed, and failed goals for NPC planning. Active goals are sorted by priority and capped at 5.

class GoalsComponent(Component):
    active_goals: list[Goal] = []          # Active goals (max 5, sorted by priority)
    completed_goals: list[UUID] = []       # Recently completed goal IDs (max 20)
    failed_goals: list[UUID] = []          # Recently failed goal IDs
    goal_generation_cooldown: float = 0.0  # Cooldown before new goal generation

Each Goal tracks: id, category (GoalCategory), description, priority, progress, conditions, deadline, source (GoalSource), target, required_resources, preferred_helpers, blockers.

GoalCategory values: ACQUIRE, CRAFT, SOCIAL, PROTECT, EXPLORE, REVENGE, AMBITION, DUTY.

Usage:

from maid_stdlib.models.npc.autonomy import GoalsComponent

goals = entity.get(GoalsComponent)
for goal in goals.active_goals:
    print(f"Goal: {goal.description} (priority={goal.priority}, progress={goal.progress})")

ScheduleComponent

Defines a daily schedule for NPC activity. Blocks map time windows to activities and locations.

class ScheduleComponent(Component):
    blocks: list[ScheduleBlock] = []               # Daily schedule blocks
    current_activity: ActivityType | None = None    # Current activity
    schedule_adherence: float = 0.8                 # How strictly NPC follows schedule
    override_until: float | None = None             # Timestamp when override expires
    override_reason: str | None = None              # Why schedule is overridden

Each ScheduleBlock has: start_hour, end_hour, activity (ActivityType), location, priority, conditions.

ActivityType values: WORK, SLEEP, EAT, SOCIALIZE, PATROL, GUARD, TRADE, CRAFT, WORSHIP, TRAIN, WANDER, CUSTOM.

Usage:

from maid_stdlib.models.npc.autonomy import ScheduleComponent, ScheduleBlock, ActivityType

entity.add(ScheduleComponent(blocks=[
    ScheduleBlock(start_hour=6, end_hour=12, activity=ActivityType.WORK,
                  location=shop_id, priority=1.0),
    ScheduleBlock(start_hour=22, end_hour=6, activity=ActivityType.SLEEP,
                  location=home_id, priority=0.9),
]))

Intent component attached to an NPC to request movement to a destination.

class NavigationIntent(Component):
    destination: UUID      # Target room ID
    reason: str            # Why the NPC is moving

SocialIntent

Intent component attached to an NPC to request a social interaction.

class SocialIntent(Component):
    target_id: UUID                    # Target NPC for interaction
    interaction_type: str              # Type of interaction
    gossip_to_share: UUID | None = None  # Gossip memory to share (if any)

SocialComponent

Runtime social state for autonomous NPC interactions.

class SocialComponent(Component):
    faction_standing: dict[str, float] = {}         # Faction reputation values
    social_influence: float = 0.5                   # NPC social influence (0.0–1.0)
    last_social_interaction: float | None = None    # Timestamp of last interaction
    social_cooldown: float = 0.0                    # Cooldown before next interaction
    interaction_reservation: UUID | None = None     # Reserved interaction partner
    reservation_expires: float | None = None        # Reservation expiry timestamp

Creating Custom Components

from maid_engine.core.ecs import Component
from pydantic import Field
from uuid import UUID


class ReputationComponent(Component):
    """Tracks entity reputation with factions."""

    faction_standing: dict[str, int] = Field(default_factory=dict)
    reputation_level: str = "neutral"

    def get_standing(self, faction: str) -> int:
        """Get standing with a faction."""
        return self.faction_standing.get(faction, 0)

    def modify_standing(self, faction: str, amount: int) -> int:
        """Modify standing, return new value."""
        current = self.get_standing(faction)
        new_value = max(-1000, min(1000, current + amount))
        self.faction_standing[faction] = new_value
        self._update_level()
        return new_value

    def _update_level(self) -> None:
        """Update reputation level based on average standing."""
        if not self.faction_standing:
            self.reputation_level = "neutral"
            return

        avg = sum(self.faction_standing.values()) / len(self.faction_standing)
        if avg >= 500:
            self.reputation_level = "revered"
        elif avg >= 100:
            self.reputation_level = "friendly"
        elif avg >= -100:
            self.reputation_level = "neutral"
        elif avg >= -500:
            self.reputation_level = "unfriendly"
        else:
            self.reputation_level = "hostile"

Component Best Practices

  1. Keep components focused: One concept per component
  2. Use sensible defaults: Make components work out of the box
  3. Validate data: Use Pydantic validators for constraints
  4. Document fields: Add docstrings and type hints
  5. Avoid logic in components: Put behavior in systems
  6. Use computed properties: For derived values