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),
]))
NavigationIntent¶
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¶
- 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