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¶
- When an entity dies, something emits
EntityDeathEvent(typically a combat system) RespawnSystem.startup()subscribes toEntityDeathEvent- The handler sets
is_dead = Trueand starts the countdown timer - Each tick,
update(delta)decrements the timer for all dead entities withRespawnComponent - When the timer hits zero, the entity is healed to full and placed back in its spawn room
EntityRespawnedEventis 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_delayeach 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¶
- ECS Systems — System lifecycle and
update() - Events Guide — Event subscription
- Creating NPCs — NPC setup patterns