diff --git a/src/mudlib/entity.py b/src/mudlib/entity.py index 216d750..43aa27e 100644 --- a/src/mudlib/entity.py +++ b/src/mudlib/entity.py @@ -26,6 +26,31 @@ class Entity(Object): defense_locked_until: float = 0.0 # monotonic time when defense recovery ends resting: bool = False # whether this entity is currently resting + @property + def posture(self) -> str: + """Return entity's current posture for room display. + + Priority order: unconscious > fighting > flying > resting > standing + """ + # Unconscious (highest priority) + if self.pl <= 0 or self.stamina <= 0: + return "unconscious" + + # Fighting (only for Player with mode="combat") + if hasattr(self, "mode") and self.mode == "combat": + return "fighting" + + # Flying (only for Player) + if getattr(self, "flying", False): + return "flying" + + # Resting + if self.resting: + return "resting" + + # Default + return "standing" + def can_accept(self, obj: Object) -> bool: """Entities accept portable Things (inventory).""" from mudlib.thing import Thing diff --git a/tests/test_entity_posture.py b/tests/test_entity_posture.py new file mode 100644 index 0000000..d7d96fd --- /dev/null +++ b/tests/test_entity_posture.py @@ -0,0 +1,172 @@ +"""Tests for Entity.posture property.""" + +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from mudlib.entity import Entity, Mob +from mudlib.player import Player + + +@pytest.fixture +def mock_writer(): + writer = MagicMock() + writer.write = MagicMock() + writer.drain = AsyncMock() + return writer + + +@pytest.fixture +def mock_reader(): + return MagicMock() + + +def test_entity_default_posture(): + """Entity with no special state should be 'standing'.""" + entity = Entity(name="Test", x=0, y=0) + assert entity.posture == "standing" + + +def test_entity_resting(): + """Entity with resting=True should be 'resting'.""" + entity = Entity(name="Test", x=0, y=0, resting=True) + assert entity.posture == "resting" + + +def test_entity_unconscious_no_pl(): + """Entity with pl <= 0 should be 'unconscious'.""" + entity = Entity(name="Test", x=0, y=0, pl=0.0) + assert entity.posture == "unconscious" + + +def test_entity_unconscious_negative_pl(): + """Entity with negative pl should be 'unconscious'.""" + entity = Entity(name="Test", x=0, y=0, pl=-10.0) + assert entity.posture == "unconscious" + + +def test_entity_unconscious_no_stamina(): + """Entity with stamina <= 0 should be 'unconscious'.""" + entity = Entity(name="Test", x=0, y=0, stamina=0.0) + assert entity.posture == "unconscious" + + +def test_entity_unconscious_negative_stamina(): + """Entity with negative stamina should be 'unconscious'.""" + entity = Entity(name="Test", x=0, y=0, stamina=-5.0) + assert entity.posture == "unconscious" + + +def test_entity_unconscious_beats_resting(): + """Unconscious priority > resting priority.""" + entity = Entity(name="Test", x=0, y=0, pl=0.0, resting=True) + assert entity.posture == "unconscious" + + +def test_player_flying(mock_reader, mock_writer): + """Player with flying=True should be 'flying'.""" + player = Player( + name="Test", x=0, y=0, reader=mock_reader, writer=mock_writer, flying=True + ) + assert player.posture == "flying" + + +def test_player_flying_beats_resting(mock_reader, mock_writer): + """Flying priority > resting priority.""" + player = Player( + name="Test", + x=0, + y=0, + reader=mock_reader, + writer=mock_writer, + flying=True, + resting=True, + ) + assert player.posture == "flying" + + +def test_player_unconscious_beats_flying(mock_reader, mock_writer): + """Unconscious priority > flying priority.""" + player = Player( + name="Test", + x=0, + y=0, + reader=mock_reader, + writer=mock_writer, + flying=True, + pl=0.0, + ) + assert player.posture == "unconscious" + + +def test_player_fighting_in_combat_mode(mock_reader, mock_writer): + """Player with mode='combat' should be 'fighting'.""" + player = Player( + name="Test", + x=0, + y=0, + reader=mock_reader, + writer=mock_writer, + mode_stack=["normal", "combat"], + ) + assert player.posture == "fighting" + + +def test_player_fighting_beats_flying(mock_reader, mock_writer): + """Fighting priority > flying priority.""" + player = Player( + name="Test", + x=0, + y=0, + reader=mock_reader, + writer=mock_writer, + mode_stack=["normal", "combat"], + flying=True, + ) + assert player.posture == "fighting" + + +def test_player_fighting_beats_resting(mock_reader, mock_writer): + """Fighting priority > resting priority.""" + player = Player( + name="Test", + x=0, + y=0, + reader=mock_reader, + writer=mock_writer, + mode_stack=["normal", "combat"], + resting=True, + ) + assert player.posture == "fighting" + + +def test_player_unconscious_beats_fighting(mock_reader, mock_writer): + """Unconscious priority > fighting priority.""" + player = Player( + name="Test", + x=0, + y=0, + reader=mock_reader, + writer=mock_writer, + mode_stack=["normal", "combat"], + stamina=0.0, + ) + assert player.posture == "unconscious" + + +def test_mob_default_posture(): + """Mob with default state should be 'standing'.""" + mob = Mob(name="Goku", x=10, y=10, description="A fighter") + assert mob.posture == "standing" + + +def test_mob_resting(): + """Mob with resting=True should be 'resting'.""" + mob = Mob(name="Goku", x=10, y=10, description="A fighter", resting=True) + assert mob.posture == "resting" + + +def test_mob_unconscious(): + """Mob with pl=0 should be 'unconscious'.""" + mob = Mob(name="Goku", x=10, y=10, description="A fighter", pl=0.0) + assert mob.posture == "unconscious"