"""Tests for safe zone combat prevention.""" import pathlib import tempfile import pytest from mudlib.combat.engine import active_encounters, get_encounter from mudlib.entity import Mob from mudlib.player import Player, players from mudlib.zone import Zone from mudlib.zones import load_zone @pytest.fixture(autouse=True) def clear_state(): """Clear global state between tests.""" players.clear() active_encounters.clear() yield players.clear() active_encounters.clear() @pytest.fixture def safe_zone(): terrain = [["." for _ in range(10)] for _ in range(10)] return Zone( name="sanctuary", width=10, height=10, terrain=terrain, safe=True, ) @pytest.fixture def unsafe_zone(): terrain = [["." for _ in range(10)] for _ in range(10)] return Zone( name="wilderness", width=10, height=10, terrain=terrain, safe=False, ) # --- Zone property tests --- def test_zone_safe_default_false(): """Zones are unsafe by default.""" zone = Zone(name="test") assert zone.safe is False def test_zone_safe_true(): """Zone can be created as safe.""" zone = Zone(name="test", safe=True) assert zone.safe is True # --- TOML loading --- def test_load_zone_safe_flag(): """Load a zone with safe = true from TOML.""" with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write( """ name = "sanctuary" description = "a peaceful place" width = 3 height = 3 safe = true [terrain] rows = [ "...", "...", "...", ] """ ) temp_path = pathlib.Path(f.name) try: zone = load_zone(temp_path) assert zone.safe is True finally: temp_path.unlink() def test_load_zone_safe_default(): """Zone without safe field defaults to False.""" with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: f.write( """ name = "wilderness" description = "dangerous lands" width = 3 height = 3 [terrain] rows = [ "...", "...", "...", ] """ ) temp_path = pathlib.Path(f.name) try: zone = load_zone(temp_path) assert zone.safe is False finally: temp_path.unlink() def test_tavern_is_safe(): """The tavern zone file has safe = true.""" project_root = pathlib.Path(__file__).resolve().parents[1] tavern_path = project_root / "content" / "zones" / "tavern.toml" zone = load_zone(tavern_path) assert zone.safe is True # --- Combat prevention --- @pytest.mark.asyncio async def test_attack_blocked_in_safe_zone(safe_zone, mock_writer, mock_reader): """Attacking in a safe zone sends error message.""" from mudlib.combat.commands import do_attack from mudlib.combat.moves import CombatMove player = Player( name="attacker", x=5, y=5, writer=mock_writer, reader=mock_reader, location=safe_zone, ) safe_zone._contents.append(player) players["attacker"] = player target = Mob( name="goblin", x=5, y=5, location=safe_zone, pl=50, stamina=40, max_stamina=40, ) safe_zone._contents.append(target) move = CombatMove( name="punch left", command="punch", variant="left", move_type="attack", stamina_cost=5, hit_time_ms=850, damage_pct=0.15, ) await do_attack(player, "goblin", move) # Should have sent the safe zone message mock_writer.write.assert_called() written = mock_writer.write.call_args_list[0][0][0] assert "can't fight here" in written.lower() # Should NOT have started combat assert get_encounter(player) is None @pytest.mark.asyncio async def test_attack_allowed_in_unsafe_zone(unsafe_zone, mock_writer, mock_reader): """Attacking in an unsafe zone works normally.""" from mudlib.combat.commands import do_attack from mudlib.combat.moves import CombatMove player = Player( name="fighter", x=5, y=5, writer=mock_writer, reader=mock_reader, location=unsafe_zone, ) unsafe_zone._contents.append(player) players["fighter"] = player target = Mob( name="goblin", x=5, y=5, location=unsafe_zone, pl=50, stamina=40, max_stamina=40, ) unsafe_zone._contents.append(target) move = CombatMove( name="punch left", command="punch", variant="left", move_type="attack", stamina_cost=5, hit_time_ms=850, damage_pct=0.15, ) await do_attack(player, "goblin", move) # Combat SHOULD have started assert get_encounter(player) is not None