mud/src/mudlib/corpse.py

93 lines
2.7 KiB
Python

"""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)