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¶
- Keep components focused: One concept per component
- Use sensible defaults: Make components work out of the box
- Validate data: Use Pydantic validators for constraints
- Document fields: Add docstrings and type hints
- Avoid logic in components: Put behavior in systems
- Use computed properties: For derived values