from pathlib import Path import pytest from engine.character import Character from engine.defs import DefRegistry from resources.logic.combat import perform_attack DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs" @pytest.fixture() def registry(): return DefRegistry.load(DEFS_DIR) def make_human(registry, **kwargs) -> Character: return Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"), **kwargs) class SequenceRng: """Deterministic stand-in: returns rolls from a fixed sequence, one call at a time.""" def __init__(self, rolls: list[int], choice_value=None): self._rolls = list(rolls) self._choice_value = choice_value def randint(self, _lo, _hi): return self._rolls.pop(0) def choice(self, seq): return self._choice_value if self._choice_value is not None else seq[0] def test_non_blocking_defender_uses_flat_difficulty(registry): attacker = make_human(registry) defender = make_human(registry) result = perform_attack(attacker, defender, registry, rng=SequenceRng([15], choice_value="torso")) assert result.blocked_check is False assert result.hit is True # 15 > BASE_DIFFICULTY(10) def test_blocking_defender_uses_opposed_check(registry): attacker = make_human(registry) attacker.bio["athletics"] = 0 defender = make_human(registry, is_blocking=True) defender.bio["athletics"] = 15 # strong defense roll # attacker rolls 12, defender rolls 5 but has a high skill -> defender wins result = perform_attack(attacker, defender, registry, rng=SequenceRng([12, 5], choice_value="torso")) assert result.blocked_check is True assert result.hit is False def test_blocking_defender_can_still_be_hit_if_attacker_beats_the_opposed_check(registry): attacker = make_human(registry) attacker.bio["athletics"] = 15 defender = make_human(registry, is_blocking=True) defender.bio["athletics"] = 0 result = perform_attack(attacker, defender, registry, rng=SequenceRng([18, 3], choice_value="torso")) assert result.blocked_check is True assert result.hit is True def test_shield_bonus_helps_the_defender_block(registry): attacker = make_human(registry) # bio athletics=0, so attacker_check == the raw roll unshielded = make_human(registry, is_blocking=True) shielded = make_human(registry, is_blocking=True) shielded.wield_item("left_hand", "wooden_shield", registry) # attacker rolls 10 (check=10), defender rolls 8 (check=8) both times; # only wooden_shield's +4 block bonus should flip the outcome result_unshielded = perform_attack(attacker, unshielded, registry, rng=SequenceRng([10, 8], choice_value="torso")) result_shielded = perform_attack(attacker, shielded, registry, rng=SequenceRng([10, 8], choice_value="torso")) assert result_unshielded.hit is True # 10 > 8 assert result_shielded.hit is False # 10 < 8 + 4 (shield bonus)