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

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

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")

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

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