Wire corpse spawning into combat death handling

When a mob dies in combat, create_corpse is called to spawn a corpse
at the mob's position with the mob's inventory transferred. This
replaces the direct despawn_mob call, making combat deaths leave
lootable corpses behind.

The fallback to despawn_mob is kept if the mob somehow has no zone.
This commit is contained in:
Jared Miller 2026-02-14 09:56:37 -05:00
parent 4f487d5178
commit 487e316629
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
2 changed files with 199 additions and 2 deletions

View file

@ -170,9 +170,16 @@ async def process_combat() -> None:
# Despawn mob losers, send victory/defeat messages # Despawn mob losers, send victory/defeat messages
if isinstance(loser, Mob): if isinstance(loser, Mob):
from mudlib.mobs import despawn_mob from mudlib.corpse import create_corpse
from mudlib.zone import Zone
despawn_mob(loser) zone = loser.location
if isinstance(zone, Zone):
create_corpse(loser, zone)
else:
from mudlib.mobs import despawn_mob
despawn_mob(loser)
await winner.send(f"You have defeated the {loser.name}!\r\n") await winner.send(f"You have defeated the {loser.name}!\r\n")
elif isinstance(winner, Mob): elif isinstance(winner, Mob):
await loser.send( await loser.send(

View file

@ -216,3 +216,193 @@ class TestCorpseAsContainer:
dummy_entity = Entity(name="dummy", x=0, y=0, location=test_zone) dummy_entity = Entity(name="dummy", x=0, y=0, location=test_zone)
assert dummy_entity.can_accept(corpse) is False assert dummy_entity.can_accept(corpse) is False
class TestCombatDeathCorpse:
"""Tests for corpse spawning when a mob dies in combat."""
@pytest.mark.asyncio
async def test_mob_death_in_combat_spawns_corpse(self, test_zone):
"""Mob death in combat spawns a corpse at mob's position."""
from mudlib.combat.encounter import CombatState
from mudlib.combat.engine import (
active_encounters,
process_combat,
start_encounter,
)
from mudlib.combat.moves import CombatMove
from mudlib.entity import Entity
# Clear active encounters
active_encounters.clear()
# Create a weak mob
mob = Mob(
name="goblin",
x=5,
y=10,
location=test_zone,
pl=1.0,
stamina=40.0,
)
mobs.append(mob)
# Create attacker
attacker = Entity(
name="hero",
x=5,
y=10,
location=test_zone,
pl=100.0,
stamina=50.0,
)
# Start encounter
encounter = start_encounter(attacker, mob)
# Set up a lethal move
punch = CombatMove(
name="punch right",
move_type="attack",
stamina_cost=5.0,
timing_window_ms=800,
damage_pct=0.15,
countered_by=["dodge left"],
)
encounter.attack(punch)
encounter.state = CombatState.RESOLVE
# Process combat to trigger resolve
await process_combat()
# Check for corpse at mob's position
corpses = [
obj for obj in test_zone.contents_at(5, 10) if isinstance(obj, Corpse)
]
assert len(corpses) == 1
assert corpses[0].name == "goblin's corpse"
@pytest.mark.asyncio
async def test_mob_death_transfers_inventory_to_corpse(self, test_zone, sword):
"""Mob death transfers inventory to corpse."""
from mudlib.combat.encounter import CombatState
from mudlib.combat.engine import (
active_encounters,
process_combat,
start_encounter,
)
from mudlib.combat.moves import CombatMove
from mudlib.entity import Entity
# Clear active encounters
active_encounters.clear()
# Create a weak mob with inventory
mob = Mob(
name="goblin",
x=5,
y=10,
location=test_zone,
pl=1.0,
stamina=40.0,
)
mobs.append(mob)
sword.move_to(mob)
# Create attacker
attacker = Entity(
name="hero",
x=5,
y=10,
location=test_zone,
pl=100.0,
stamina=50.0,
)
# Start encounter and kill mob
encounter = start_encounter(attacker, mob)
punch = CombatMove(
name="punch right",
move_type="attack",
stamina_cost=5.0,
timing_window_ms=800,
damage_pct=0.15,
countered_by=["dodge left"],
)
encounter.attack(punch)
encounter.state = CombatState.RESOLVE
# Process combat
await process_combat()
# Find corpse
corpses = [
obj for obj in test_zone.contents_at(5, 10) if isinstance(obj, Corpse)
]
assert len(corpses) == 1
corpse = corpses[0]
# Verify sword is in corpse
assert sword in corpse._contents
assert sword.location is corpse
@pytest.mark.asyncio
async def test_corpse_appears_in_zone_contents(self, test_zone):
"""Corpse appears in zone.contents_at after mob death."""
from mudlib.combat.encounter import CombatState
from mudlib.combat.engine import (
active_encounters,
process_combat,
start_encounter,
)
from mudlib.combat.moves import CombatMove
from mudlib.entity import Entity
# Clear active encounters
active_encounters.clear()
# Create a weak mob
mob = Mob(
name="goblin",
x=5,
y=10,
location=test_zone,
pl=1.0,
stamina=40.0,
)
mobs.append(mob)
# Create attacker
attacker = Entity(
name="hero",
x=5,
y=10,
location=test_zone,
pl=100.0,
stamina=50.0,
)
# Start encounter and kill mob
encounter = start_encounter(attacker, mob)
punch = CombatMove(
name="punch right",
move_type="attack",
stamina_cost=5.0,
timing_window_ms=800,
damage_pct=0.15,
countered_by=["dodge left"],
)
encounter.attack(punch)
encounter.state = CombatState.RESOLVE
# Process combat
await process_combat()
# Verify corpse is in zone contents
contents = list(test_zone.contents_at(5, 10))
corpse_count = sum(1 for obj in contents if isinstance(obj, Corpse))
assert corpse_count == 1
# Verify it's the goblin's corpse
corpse = next(obj for obj in contents if isinstance(obj, Corpse))
assert corpse.name == "goblin's corpse"