Handle mob defeat in combat resolution
Phase 4: when combat ends, determine winner/loser. If the loser is a Mob, despawn it and send a victory message to the winner. If the loser is a Player fighting a Mob, send a defeat message instead.
This commit is contained in:
parent
ca53357730
commit
91c1af86e2
2 changed files with 124 additions and 1 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from mudlib.combat.encounter import IDLE_TIMEOUT, CombatEncounter, CombatState
|
from mudlib.combat.encounter import IDLE_TIMEOUT, CombatEncounter, CombatState
|
||||||
from mudlib.entity import Entity
|
from mudlib.entity import Entity, Mob
|
||||||
|
|
||||||
# Global list of active combat encounters
|
# Global list of active combat encounters
|
||||||
active_encounters: list[CombatEncounter] = []
|
active_encounters: list[CombatEncounter] = []
|
||||||
|
|
@ -101,6 +101,27 @@ async def process_combat() -> None:
|
||||||
await encounter.defender.send(result.defender_msg + "\r\n")
|
await encounter.defender.send(result.defender_msg + "\r\n")
|
||||||
|
|
||||||
if result.combat_ended:
|
if result.combat_ended:
|
||||||
|
# Determine winner/loser
|
||||||
|
if encounter.defender.pl <= 0:
|
||||||
|
loser = encounter.defender
|
||||||
|
winner = encounter.attacker
|
||||||
|
else:
|
||||||
|
loser = encounter.attacker
|
||||||
|
winner = encounter.defender
|
||||||
|
|
||||||
|
# Despawn mob losers, send victory/defeat messages
|
||||||
|
if isinstance(loser, Mob):
|
||||||
|
from mudlib.mobs import despawn_mob
|
||||||
|
|
||||||
|
despawn_mob(loser)
|
||||||
|
await winner.send(
|
||||||
|
f"You have defeated the {loser.name}!\r\n"
|
||||||
|
)
|
||||||
|
elif isinstance(winner, Mob):
|
||||||
|
await loser.send(
|
||||||
|
f"You have been defeated by the {winner.name}!\r\n"
|
||||||
|
)
|
||||||
|
|
||||||
# Pop combat mode from both entities if they're Players
|
# Pop combat mode from both entities if they're Players
|
||||||
from mudlib.player import Player
|
from mudlib.player import Player
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import pytest
|
||||||
|
|
||||||
import mudlib.commands.movement as movement_mod
|
import mudlib.commands.movement as movement_mod
|
||||||
from mudlib.combat import commands as combat_commands
|
from mudlib.combat import commands as combat_commands
|
||||||
|
from mudlib.combat.encounter import CombatState
|
||||||
from mudlib.combat.engine import active_encounters, get_encounter
|
from mudlib.combat.engine import active_encounters, get_encounter
|
||||||
from mudlib.combat.moves import load_moves
|
from mudlib.combat.moves import load_moves
|
||||||
from mudlib.entity import Mob
|
from mudlib.entity import Mob
|
||||||
|
|
@ -406,3 +407,104 @@ class TestViewportRendering:
|
||||||
assert "*" not in stripped
|
assert "*" not in stripped
|
||||||
|
|
||||||
look_mod.world = old
|
look_mod.world = old
|
||||||
|
|
||||||
|
|
||||||
|
# --- Phase 4: mob defeat tests ---
|
||||||
|
|
||||||
|
|
||||||
|
class TestMobDefeat:
|
||||||
|
@pytest.fixture
|
||||||
|
def goblin_mob(self, goblin_toml):
|
||||||
|
template = load_mob_template(goblin_toml)
|
||||||
|
return spawn_mob(template, 0, 0)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_mob_despawned_on_pl_zero(
|
||||||
|
self, player, goblin_mob, punch_right
|
||||||
|
):
|
||||||
|
"""Mob with PL <= 0 gets despawned after combat resolves."""
|
||||||
|
from mudlib.combat.engine import process_combat, start_encounter
|
||||||
|
|
||||||
|
encounter = start_encounter(player, goblin_mob)
|
||||||
|
player.mode_stack.append("combat")
|
||||||
|
|
||||||
|
# Set mob PL very low so attack kills it
|
||||||
|
goblin_mob.pl = 1.0
|
||||||
|
|
||||||
|
# Attack and force resolution
|
||||||
|
encounter.attack(punch_right)
|
||||||
|
encounter.state = CombatState.RESOLVE
|
||||||
|
|
||||||
|
await process_combat()
|
||||||
|
|
||||||
|
assert goblin_mob not in mobs
|
||||||
|
assert goblin_mob.alive is False
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_player_gets_victory_message(
|
||||||
|
self, player, goblin_mob, punch_right
|
||||||
|
):
|
||||||
|
"""Player receives a victory message when mob is defeated."""
|
||||||
|
from mudlib.combat.engine import process_combat, start_encounter
|
||||||
|
|
||||||
|
encounter = start_encounter(player, goblin_mob)
|
||||||
|
player.mode_stack.append("combat")
|
||||||
|
|
||||||
|
goblin_mob.pl = 1.0
|
||||||
|
encounter.attack(punch_right)
|
||||||
|
encounter.state = CombatState.RESOLVE
|
||||||
|
|
||||||
|
await process_combat()
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
call[0][0] for call in player.writer.write.call_args_list
|
||||||
|
]
|
||||||
|
assert any("defeated" in msg.lower() for msg in messages)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_mob_stamina_depleted_despawns(
|
||||||
|
self, player, goblin_mob, punch_right
|
||||||
|
):
|
||||||
|
"""Mob is despawned when attacker stamina depleted (combat end)."""
|
||||||
|
from mudlib.combat.engine import process_combat, start_encounter
|
||||||
|
|
||||||
|
encounter = start_encounter(player, goblin_mob)
|
||||||
|
player.mode_stack.append("combat")
|
||||||
|
|
||||||
|
# Drain player stamina so combat ends on exhaustion
|
||||||
|
player.stamina = 0.0
|
||||||
|
encounter.attack(punch_right)
|
||||||
|
encounter.state = CombatState.RESOLVE
|
||||||
|
|
||||||
|
await process_combat()
|
||||||
|
|
||||||
|
# Encounter should have ended
|
||||||
|
assert get_encounter(player) is None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_player_defeat_not_despawned(
|
||||||
|
self, player, goblin_mob, punch_right
|
||||||
|
):
|
||||||
|
"""When player loses, player is not despawned."""
|
||||||
|
from mudlib.combat.engine import process_combat, start_encounter
|
||||||
|
|
||||||
|
# Mob attacks player — mob is attacker, player is defender
|
||||||
|
encounter = start_encounter(goblin_mob, player)
|
||||||
|
player.mode_stack.append("combat")
|
||||||
|
|
||||||
|
player.pl = 1.0
|
||||||
|
encounter.attack(punch_right)
|
||||||
|
encounter.state = CombatState.RESOLVE
|
||||||
|
|
||||||
|
await process_combat()
|
||||||
|
|
||||||
|
# Player should get defeat message, not be despawned
|
||||||
|
messages = [
|
||||||
|
call[0][0] for call in player.writer.write.call_args_list
|
||||||
|
]
|
||||||
|
assert any(
|
||||||
|
"defeated" in msg.lower() or "damage" in msg.lower()
|
||||||
|
for msg in messages
|
||||||
|
)
|
||||||
|
# Player is still in players dict (not removed)
|
||||||
|
assert player.name in players
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue