Skip to content

Respawning Monster

Problem

You want a monster that fights players, dies, then respawns at its spawn point after a configurable delay.

Solution

The Respawn Component

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


class RespawnComponent(Component):
    """Tracks respawn state for an entity."""

    spawn_room_id: UUID
    respawn_delay: float = 300.0  # Seconds until respawn
    time_until_respawn: float = 0.0
    is_dead: bool = False
    template_name: str = ""  # For recreating with full stats

The Respawn System

from dataclasses import dataclass
from uuid import UUID

from maid_engine.core.ecs import System, Entity
from maid_engine.core.events import Event
from maid_stdlib.components import (
    DescriptionComponent,
    HealthComponent,
    PositionComponent,
)
from maid_stdlib.events import EntityDeathEvent, MessageEvent


@dataclass
class EntityRespawnedEvent(Event):
    """Fired when a dead entity respawns."""
    entity_id: UUID
    room_id: UUID


class RespawnSystem(System):
    """Handles death and timed respawning of entities."""

    priority = 90

    async def startup(self) -> None:
        self.events.subscribe(EntityDeathEvent, self._on_death)

    async def _on_death(self, event: EntityDeathEvent) -> None:
        """Mark the entity as dead and start the respawn timer."""
        entity = self.world.get_entity(event.entity_id)
        if not entity:
            return

        respawn = entity.try_get(RespawnComponent)
        if not respawn:
            return  # Non-respawning entity, ignore

        respawn.is_dead = True
        respawn.time_until_respawn = respawn.respawn_delay

        # Announce death to the room
        room_id = self.world.get_entity_room(entity.id)
        if room_id:
            desc = entity.try_get(DescriptionComponent)
            name = desc.name if desc else "Something"
            await self._announce_to_room(
                room_id, f"{name} collapses and dies!\n"
            )
            self.world.remove_entity_from_room(entity.id)

    async def update(self, delta: float) -> None:
        """Tick down respawn timers and respawn when ready."""
        for entity in self.entities.with_components(RespawnComponent):
            respawn = entity.get(RespawnComponent)
            if not respawn.is_dead:
                continue

            respawn.time_until_respawn -= delta
            if respawn.time_until_respawn <= 0:
                await self._respawn(entity, respawn)

    async def _respawn(
        self, entity: Entity, respawn: RespawnComponent
    ) -> None:
        """Restore the entity to life at its spawn point."""
        respawn.is_dead = False
        respawn.time_until_respawn = 0.0

        # Restore health to full
        health = entity.try_get(HealthComponent)
        if health:
            health.heal(health.maximum)

        # Place back in spawn room
        self.world.place_entity_in_room(entity.id, respawn.spawn_room_id)

        # Announce
        desc = entity.try_get(DescriptionComponent)
        name = desc.name if desc else "Something"
        await self._announce_to_room(
            respawn.spawn_room_id,
            f"{name} materializes out of thin air!\n",
        )

        await self.events.emit(EntityRespawnedEvent(
            entity_id=entity.id,
            room_id=respawn.spawn_room_id,
        ))

    async def _announce_to_room(self, room_id: UUID, message: str) -> None:
        """Send a message to all players in a room."""
        target_ids = [
            entity.id
            for entity in self.world.entities_in_room(room_id)
            if entity.has_tag("player")
        ]
        if target_ids:
            await self.events.emit(MessageEvent(
                sender_id=None,
                target_ids=target_ids,
                channel="room",
                message=message,
            ))

Creating a Respawning Monster

from uuid import UUID
from maid_engine.core.ecs import Entity
from maid_engine.core.world import World
from maid_stdlib.components import (
    DescriptionComponent,
    HealthComponent,
    NPCComponent,
)


async def create_goblin(world: World, room_id: UUID) -> Entity:
    """Create a goblin that respawns 60 seconds after death."""
    goblin = world.create_entity()
    goblin.add(DescriptionComponent(
        name="Goblin Scout",
        short_desc="a snarling goblin scout",
        long_desc="A wiry goblin with sharp teeth and a rusty dagger.",
        keywords=["goblin", "scout"],
    ))
    goblin.add(HealthComponent(current=30, maximum=30, regeneration_rate=0.0))
    goblin.add(NPCComponent(
        behavior_type="aggressive",
        respawn_time=60.0,
        spawn_point_id=room_id,
    ))
    goblin.add(RespawnComponent(
        spawn_room_id=room_id,
        respawn_delay=60.0,
        template_name="goblin_scout",
    ))
    goblin.add_tag("npc")
    goblin.add_tag("hostile")
    world.place_entity_in_room(goblin.id, room_id)
    return goblin

Registering in Your Content Pack

from maid_engine.core.world import World
from maid_engine.core.ecs import System
from maid_engine.core.events import Event
from maid_engine.plugins import ContentPackManifest


class MyContentPack:
    @property
    def manifest(self) -> ContentPackManifest:
        return ContentPackManifest(
            name="my-pack",
            version="1.0.0",
            description="My custom content pack",
        )

    def get_systems(self, world: World) -> list[System]:
        return [RespawnSystem(world)]

    def get_events(self) -> list[type[Event]]:
        return [EntityDeathEvent, EntityRespawnedEvent]

How It Works

  1. When an entity dies, something emits EntityDeathEvent (typically a combat system)
  2. RespawnSystem.startup() subscribes to EntityDeathEvent
  3. The handler sets is_dead = True and starts the countdown timer
  4. Each tick, update(delta) decrements the timer for all dead entities with RespawnComponent
  5. When the timer hits zero, the entity is healed to full and placed back in its spawn room
  6. EntityRespawnedEvent is emitted so other systems can react (e.g., reset aggro)

Variations

  • Loot on death: Before removing from room, create a corpse entity with loot items
  • Scaling respawn: Increase respawn_delay each time the mob dies (diminishing returns farming)
  • Boss respawn: Set a much longer delay and emit a server-wide announcement
  • Despawn instead: Set a max death count — after N deaths, destroy the entity permanently
  • Random spawn room: Pick from a list of rooms instead of always using the same one

See Also