Skip to content

Events Reference

This reference documents all built-in events in MAID. Events are organized by their source package.

Event Base Class

All events inherit from Event:

from dataclasses import dataclass
from datetime import datetime

@dataclass
class Event:
    """Base class for all events."""

    event_type: str      # Auto-set to class name
    timestamp: datetime  # Auto-set to current time
    cancelled: bool      # Set to True to stop propagation

    def cancel(self) -> None:
        """Cancel this event."""
        self.cancelled = True

Engine Events (maid-engine)

Core lifecycle events from the engine.

TickEvent

Emitted at the start of each game tick.

@dataclass
class TickEvent(Event):
    tick_number: int   # Sequential tick counter
    delta: float       # Time since last tick in seconds

Use cases:

  • Time-based processing
  • Periodic tasks
  • Animation/effect timing

Example:

async def on_tick(event: TickEvent):
    if event.tick_number % 4 == 0:  # Every second at 4 ticks/sec
        await process_regeneration()

world.events.subscribe(TickEvent, on_tick)

EntityCreatedEvent

Emitted when an entity is created.

@dataclass
class EntityCreatedEvent(Event):
    entity_id: UUID    # ID of the created entity

Use cases:

  • Initialize entity state
  • Register entity in indexes
  • Trigger spawn effects

EntityDestroyedEvent

Emitted when an entity is destroyed.

@dataclass
class EntityDestroyedEvent(Event):
    entity_id: UUID    # ID of the destroyed entity

Use cases:

  • Clean up entity state
  • Remove from indexes
  • Trigger death/despawn effects

ComponentAddedEvent

Emitted when a component is added to an entity.

@dataclass
class ComponentAddedEvent(Event):
    entity_id: UUID        # Entity that received the component
    component_type: str    # Type name of the component

Use cases:

  • React to capability changes
  • Update system indexes
  • Trigger effect application

ComponentRemovedEvent

Emitted when a component is removed from an entity.

@dataclass
class ComponentRemovedEvent(Event):
    entity_id: UUID        # Entity that lost the component
    component_type: str    # Type name of the component

Use cases:

  • React to capability loss
  • Update system indexes
  • Trigger effect removal

PlayerConnectedEvent

Emitted when a player connects to the server.

@dataclass
class PlayerConnectedEvent(Event):
    session_id: UUID           # Network session ID
    player_id: UUID | None     # Entity ID (None if not yet logged in)

Use cases:

  • Initialize session state
  • Send welcome messages
  • Track connection statistics

PlayerDisconnectedEvent

Emitted when a player disconnects from the server.

@dataclass
class PlayerDisconnectedEvent(Event):
    session_id: UUID           # Network session ID
    player_id: UUID | None     # Entity ID (None if not logged in)

Use cases:

  • Save player state
  • Clean up session
  • Notify other players

PlayerCommandEvent

Emitted before a player command is processed.

@dataclass
class PlayerCommandEvent(Event):
    player_id: UUID        # Player entity ID
    command: str           # Command name
    args: list[str]        # Command arguments

Use cases:

  • Command logging
  • Rate limiting
  • Command interception

Example:

async def log_commands(event: PlayerCommandEvent):
    logger.info(f"Player {event.player_id} executed: {event.command}")

world.events.subscribe(PlayerCommandEvent, log_commands)

RoomEnterEvent

Emitted when an entity enters a room.

@dataclass
class RoomEnterEvent(Event):
    entity_id: UUID            # Entity entering
    room_id: UUID              # Room being entered
    from_room_id: UUID | None  # Previous room (None if spawning)

Use cases:

  • Send room descriptions
  • Trigger room scripts
  • Notify other occupants

RoomLeaveEvent

Emitted when an entity leaves a room.

@dataclass
class RoomLeaveEvent(Event):
    entity_id: UUID            # Entity leaving
    room_id: UUID              # Room being left
    to_room_id: UUID | None    # Destination room (None if despawning)

Use cases:

  • Update room state
  • Notify other occupants
  • Trigger exit scripts

CustomEvent

Generic event for ad-hoc use without creating a new event class.

@dataclass
class CustomEvent(Event):
    name: str              # Custom event name
    data: dict[str, Any]   # Event data

Use cases:

  • Quick prototyping
  • Dynamic event types
  • Plugin communication

Example:

# Emit custom event
await world.events.emit(CustomEvent(
    name="weather_change",
    data={"weather": "rain", "intensity": 0.5}
))

# Subscribe to custom events
async def on_custom(event: CustomEvent):
    if event.name == "weather_change":
        weather = event.data["weather"]
        # Handle weather change

world.events.subscribe(CustomEvent, on_custom)

Standard Library Events (maid-stdlib)

Common game events from the standard library.

CombatStartEvent

Emitted when combat begins between entities.

@dataclass
class CombatStartEvent(Event):
    attacker_id: UUID      # Entity initiating combat
    defender_id: UUID      # Entity being attacked
    room_id: UUID          # Room where combat occurs

Use cases:

  • Initialize combat state
  • Notify players
  • Block certain actions during combat

CombatEndEvent

Emitted when combat ends.

@dataclass
class CombatEndEvent(Event):
    combatant_ids: list[UUID]  # All combatants involved
    room_id: UUID              # Room where combat occurred

Use cases:

  • Clean up combat state
  • Award experience
  • Enable post-combat actions

DamageDealtEvent

Emitted when damage is dealt to an entity.

@dataclass
class DamageDealtEvent(Event):
    source_id: UUID | None     # Damage source (None for environmental)
    target_id: UUID            # Entity receiving damage
    damage: int                # Amount of damage
    damage_type: str           # Type: "physical", "fire", "ice", etc.
    hit_location: str | None   # Body location hit (optional)

Use cases:

  • Update health displays
  • Trigger damage reactions
  • Log combat statistics
  • Apply damage-based effects

Example:

async def on_damage(event: DamageDealtEvent):
    # Check for death
    target = world.entities.get(event.target_id)
    if target:
        health = target.try_get(HealthComponent)
        if health and not health.is_alive:
            await world.events.emit(EntityDeathEvent(
                entity_id=event.target_id,
                killer_id=event.source_id,
            ))

world.events.subscribe(DamageDealtEvent, on_damage)

EntityDeathEvent

Emitted when an entity dies.

@dataclass
class EntityDeathEvent(Event):
    entity_id: UUID            # Entity that died
    killer_id: UUID | None     # Entity that killed them (if any)

Use cases:

  • Drop loot
  • Award experience
  • Respawn handling
  • Death notifications

ItemPickedUpEvent

Emitted when an item is picked up.

@dataclass
class ItemPickedUpEvent(Event):
    entity_id: UUID    # Entity picking up
    item_id: UUID      # Item being picked up
    room_id: UUID      # Room where pickup occurred

Use cases:

  • Update inventories
  • Quest progress tracking
  • Achievement tracking

ItemDroppedEvent

Emitted when an item is dropped.

@dataclass
class ItemDroppedEvent(Event):
    entity_id: UUID    # Entity dropping
    item_id: UUID      # Item being dropped
    room_id: UUID      # Room where drop occurred

Use cases:

  • Update inventories
  • Spawn item in room
  • Trigger item scripts

MessageEvent

Emitted for communication messages (say, tell, channel).

@dataclass
class MessageEvent(Event):
    sender_id: UUID | None     # Sender (None for system messages)
    target_ids: list[UUID]     # Recipients
    channel: str               # Channel name: "say", "tell", "ooc", etc.
    message: str               # Message content

Use cases:

  • Message routing
  • Chat logging
  • Profanity filtering
  • Bridge integration

Event Priority

Events are processed in handler priority order:

from maid_engine.core.events import EventPriority

class EventPriority(Enum):
    HIGHEST = auto()   # Runs first - validation, blocking
    HIGH = auto()      # Pre-processing
    NORMAL = auto()    # Standard handlers
    LOW = auto()       # Post-processing
    LOWEST = auto()    # Runs last - logging, cleanup

Subscribing to Events

from maid_engine.core.events import EventPriority

# Basic subscription
world.events.subscribe(DamageDealtEvent, my_handler)

# With priority
world.events.subscribe(
    DamageDealtEvent,
    validation_handler,
    priority=EventPriority.HIGHEST
)

# One-time subscription
world.events.subscribe(
    PlayerConnectedEvent,
    first_player_bonus,
    once=True
)

Emitting Events

# Async emission (immediate)
await world.events.emit(DamageDealtEvent(
    source_id=attacker.id,
    target_id=target.id,
    damage=25,
    damage_type="physical",
))

# Sync emission (queued)
world.events.emit_sync(MyEvent(...))

# Process queued events
await world.events.process_pending()

Canceling Events

async def block_damage_in_safe_zone(event: DamageDealtEvent):
    target = world.entities.get(event.target_id)
    if target and is_in_safe_zone(target):
        event.cancel()  # Prevents further processing

world.events.subscribe(
    DamageDealtEvent,
    block_damage_in_safe_zone,
    priority=EventPriority.HIGHEST
)

Creating Custom Events

from dataclasses import dataclass
from uuid import UUID
from maid_engine.core.events import Event


@dataclass
class QuestCompletedEvent(Event):
    """Emitted when a player completes a quest."""

    player_id: UUID
    quest_id: UUID
    quest_name: str
    experience_reward: int = 0
    gold_reward: int = 0


# Use in your content pack
def get_events(self) -> list[type[Event]]:
    return [QuestCompletedEvent]

Event Best Practices

  1. Keep events immutable: Don't modify event data in handlers
  2. Use specific event types: Avoid overly generic events
  3. Document event flow: Comment the expected sequence of events
  4. Handle errors gracefully: Don't let handler errors crash the system
  5. Clean up subscriptions: Unsubscribe when systems shut down
  6. Use appropriate priorities: Validation early, logging late