mud/tests/test_combat_zaxis.py
Jared Miller edbad4666f
Rework combat state machine
PENDING phase, defense active/recovery windows
2026-02-16 12:17:34 -05:00

224 lines
6.4 KiB
Python

"""Tests for z-axis altitude checks in combat."""
from pathlib import Path
import pytest
from mudlib.combat import commands as combat_commands
from mudlib.combat.encounter import CombatState
from mudlib.combat.engine import get_encounter, start_encounter
from mudlib.combat.moves import load_moves
@pytest.fixture
def target(mock_reader, mock_writer, test_zone):
"""Alias for nearby_player fixture with name 'target' for this module."""
from mudlib.player import Player, players
t = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
t.location = test_zone
test_zone._contents.append(t)
players[t.name] = t
return t
@pytest.fixture
def moves():
"""Load combat moves from content directory."""
content_dir = Path(__file__).parent.parent / "content" / "combat"
return load_moves(content_dir)
@pytest.fixture(autouse=True)
def inject_moves(moves):
"""Inject loaded moves into combat commands module."""
combat_commands.combat_moves = moves
yield
combat_commands.combat_moves = {}
@pytest.fixture
def punch_right(moves):
"""Get the punch right move."""
return moves["punch right"]
@pytest.fixture
def dodge_left(moves):
"""Get the dodge left move."""
return moves["dodge left"]
# --- Z-axis altitude check tests for starting combat ---
@pytest.mark.asyncio
async def test_attack_fails_when_attacker_flying_defender_grounded(
player, target, punch_right
):
"""Test attack fails when attacker is flying and defender is grounded."""
player.flying = True
target.flying = False
await combat_commands.do_attack(player, "Vegeta", punch_right)
# Combat should not start
encounter = get_encounter(player)
assert encounter is None
# Player should get error message
player.writer.write.assert_called()
messages = [call[0][0] for call in player.writer.write.call_args_list]
assert any("can't reach" in msg.lower() for msg in messages)
@pytest.mark.asyncio
async def test_attack_fails_when_attacker_grounded_defender_flying(
player, target, punch_right
):
"""Test attack fails when attacker is grounded and defender is flying."""
player.flying = False
target.flying = True
await combat_commands.do_attack(player, "Vegeta", punch_right)
# Combat should not start
encounter = get_encounter(player)
assert encounter is None
# Player should get error message
player.writer.write.assert_called()
messages = [call[0][0] for call in player.writer.write.call_args_list]
assert any("can't reach" in msg.lower() for msg in messages)
@pytest.mark.asyncio
async def test_attack_succeeds_when_both_flying(player, target, punch_right):
"""Test attack succeeds when both are flying."""
player.flying = True
target.flying = True
await combat_commands.do_attack(player, "Vegeta", punch_right)
# Combat should start
encounter = get_encounter(player)
assert encounter is not None
assert encounter.attacker is player
assert encounter.defender is target
@pytest.mark.asyncio
async def test_attack_succeeds_when_both_grounded(player, target, punch_right):
"""Test attack succeeds when both are grounded."""
player.flying = False
target.flying = False
await combat_commands.do_attack(player, "Vegeta", punch_right)
# Combat should start
encounter = get_encounter(player)
assert encounter is not None
assert encounter.attacker is player
assert encounter.defender is target
# --- Flying dodge tests (altitude mismatch at resolve time) ---
@pytest.mark.asyncio
async def test_flying_during_window_causes_miss(player, target, punch_right):
"""Test flying during window phase causes attack to miss at resolve time."""
# Both grounded, start combat
player.flying = False
target.flying = False
encounter = start_encounter(player, target)
encounter.attack(punch_right)
# Advance to WINDOW phase
encounter.state = CombatState.PENDING
# Defender flies during window
target.flying = True
# Resolve
result = encounter.resolve()
# Attack should miss (countered or zero damage)
assert result.countered is True or result.damage == 0.0
@pytest.mark.asyncio
async def test_both_flying_at_resolve_attack_lands(player, target, punch_right):
"""Test both flying at resolve time — attack lands normally."""
# Both flying, start combat
player.flying = True
target.flying = True
encounter = start_encounter(player, target)
encounter.attack(punch_right)
# Advance to WINDOW phase (no altitude change)
encounter.state = CombatState.PENDING
# Resolve
result = encounter.resolve()
# Attack should land (damage > 0 unless defended)
# Since there's no defense, damage should be > 0
assert result.damage > 0.0
@pytest.mark.asyncio
async def test_attacker_flies_during_window_causes_miss(player, target, punch_right):
"""Test attacker flying during window phase causes attack to miss."""
# Both grounded, start combat
player.flying = False
target.flying = False
encounter = start_encounter(player, target)
encounter.attack(punch_right)
# Advance to WINDOW phase
encounter.state = CombatState.PENDING
# Attacker flies during window
player.flying = True
# Resolve
result = encounter.resolve()
# Attack should miss
assert result.countered is True or result.damage == 0.0
@pytest.mark.asyncio
async def test_flying_dodge_messages_correct_grammar(player, target, punch_right):
"""Test flying dodge produces grammatically correct messages from both POVs."""
from mudlib.render.pov import render_pov
# Both grounded, start combat
player.flying = False
target.flying = False
encounter = start_encounter(player, target)
encounter.attack(punch_right)
# Advance to WINDOW phase
encounter.state = CombatState.PENDING
# Defender flies during window
target.flying = True
# Resolve
result = encounter.resolve()
# Render from both POVs
attacker_msg = render_pov(result.resolve_template, player, player, target)
defender_msg = render_pov(result.resolve_template, target, player, target)
# Attacker POV should say "You miss — Vegeta is out of reach!"
assert attacker_msg == "You miss — Vegeta is out of reach!"
# Defender POV should say "Goku misses — You are out of reach!"
assert defender_msg == "Goku misses — You are out of reach!"