diff --git a/docs/how/loot-and-corpses.rst b/docs/how/loot-and-corpses.rst new file mode 100644 index 0000000..7e12eaf --- /dev/null +++ b/docs/how/loot-and-corpses.rst @@ -0,0 +1,104 @@ +======================= +loot and corpse system +======================= + +the death system has three phases: knockout, finisher, and decomposition. +corpses are containers that rot over time. loot is probabilistic. + +loot tables +=========== + +loot is defined per-mob in toml with ``LootEntry`` records:: + + [[loot]] + name = "crude club" + chance = 0.8 + description = "a crude wooden club" + + [[loot]] + name = "gold coin" + chance = 0.5 + min_count = 1 + max_count = 3 + +fields: + +- ``name`` - item name +- ``chance`` - probability (0.0-1.0) to drop +- ``min_count`` / ``max_count`` - quantity range (defaults to 1) +- ``description`` - item description string + +``roll_loot()`` processes the table entry by entry. for each entry, it rolls +random against chance. if successful, picks a count in the min/max range and +creates that many ``Thing`` objects. returns a flat list of items. + +death flow +========== + +knockout (automatic) +-------------------- + +when stamina or pl drops to/below zero, the entity's ``posture`` becomes +``"unconscious"``. the mob stays registered in ``mobs`` dict. no corpse is +created yet. items stay in the mob's inventory. + +finisher (explicit) +------------------- + +requires a command (e.g. ``snapneck``) targeting an unconscious entity. sets +``pl`` to -100 (dead). loads mob template to get loot table. calls +``create_corpse()`` with the mob, zone, and loot table. + +no corpse until finisher — knockout alone doesn't drop loot. + +corpse creation +=============== + +``create_corpse(mob, zone, ttl=300, loot_table=None)`` + +1. creates ``Corpse`` object (extends ``Container``) at mob's coords +2. names it ``"{mob.name}'s corpse"`` +3. transfers all items from mob inventory to corpse +4. rolls loot table, adds generated items to corpse +5. calls ``despawn_mob()`` to remove mob from world +6. registers corpse in global ``active_corpses`` list +7. returns the corpse + +corpse properties: + +- ``closed=False`` - always open +- ``portable=False`` - can't be picked up +- ``decompose_at`` - timestamp (now + ttl seconds) + +corpses work with existing item commands (``get``, ``put``, ``look in``) because +they're containers. + +decomposition +============= + +``process_decomposing()`` runs every game loop tick. checks each corpse in +``active_corpses`` against current time. when ``decompose_at`` passes: + +1. broadcasts "X decomposes." to entities on same tile +2. clears all items (items rot with corpse — no loot recovery) +3. removes corpse from world +4. removes from ``active_corpses`` registry + +ttl-based cleanup ensures corpses don't clutter the world. default 300s (5min). + +code +==== + +implementation: + +- ``src/mudlib/loot.py`` - loot table dataclass and roll logic +- ``src/mudlib/corpse.py`` - Corpse container, creation, decomposition +- ``src/mudlib/commands/snapneck.py`` - finisher command +- ``content/mobs/*.toml`` - per-mob loot tables + +related systems: + +- ``src/mudlib/combat.py`` - knockout logic (posture="unconscious") +- ``src/mudlib/entity.py`` - Entity.posture, inventory +- ``src/mudlib/item.py`` - Thing, Container base classes +- ``src/mudlib/server.py`` - calls process_decomposing() each tick