"""Corpse — a container left behind when an entity dies.""" from __future__ import annotations import time from dataclasses import dataclass from mudlib.container import Container from mudlib.entity import Mob from mudlib.loot import LootEntry, roll_loot from mudlib.mobs import despawn_mob from mudlib.zone import Zone # Module-level registry of active corpses active_corpses: list[Corpse] = [] @dataclass(eq=False) class Corpse(Container): """A corpse left behind when an entity dies. Corpses are containers that hold the deceased entity's inventory. They are not portable (can't be picked up) and are always open. They have a decompose_at timestamp for eventual cleanup. """ portable: bool = False closed: bool = False decompose_at: float = 0.0 def create_corpse( mob: Mob, zone: Zone, ttl: int = 300, loot_table: list[LootEntry] | None = None ) -> Corpse: """Create a corpse from a defeated mob. Args: mob: The mob that died zone: The zone where the corpse will be placed ttl: Time to live in seconds (default 300) loot_table: Optional loot table to roll for additional items Returns: The created corpse with the mob's inventory and loot """ # Create corpse at mob's position corpse = Corpse( name=f"{mob.name}'s corpse", location=zone, x=mob.x, y=mob.y, decompose_at=time.monotonic() + ttl, ) # Transfer mob's inventory to corpse for item in list(mob._contents): item.move_to(corpse) # Roll and add loot if loot_table: for item in roll_loot(loot_table): item.move_to(corpse) # Remove mob from world despawn_mob(mob) # Register corpse for decomposition tracking active_corpses.append(corpse) return corpse async def process_decomposing() -> None: """Remove expired corpses and broadcast decomposition messages.""" now = time.monotonic() for corpse in active_corpses[:]: # copy to allow modification if now >= corpse.decompose_at: # Broadcast to entities at the same tile zone = corpse.location if isinstance(zone, Zone) and corpse.x is not None and corpse.y is not None: from mudlib.entity import Entity for obj in zone.contents_at(corpse.x, corpse.y): if isinstance(obj, Entity): await obj.send(f"{corpse.name} decomposes.\r\n") # Clear contents — items rot with the corpse for item in list(corpse._contents): item.move_to(None) # Remove corpse from world corpse.move_to(None) active_corpses.remove(corpse)