from pathlib import Path import pytest from engine.character import ClothingPiece, Character from engine.defs import DefRegistry from resources.logic.combat import defender_armor, 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 FixedRng: def __init__(self, roll: int, choice_value=None): self._roll = roll self._choice_value = choice_value def randint(self, _lo, _hi): return self._roll def choice(self, seq): return self._choice_value if self._choice_value is not None else seq[0] def test_defender_armor_sums_worn_clothing(registry): defender = make_human(registry, clothing=[ClothingPiece("torso", "leather_jacket")]) assert defender_armor(defender, registry) == pytest.approx(3.0) # leather_jacket's armor_reduction def test_unarmored_defender_has_zero_armor(registry): defender = make_human(registry) assert defender_armor(defender, registry) == 0.0 def test_armor_reduces_damage_taken(registry): attacker = make_human(registry) attacker.bio["athletics"] = 10 unarmored = make_human(registry) armored = make_human(registry, clothing=[ClothingPiece("torso", "leather_jacket")]) unarmored_result = perform_attack(attacker, unarmored, registry, rng=FixedRng(roll=15, choice_value="torso")) armored_result = perform_attack(attacker, armored, registry, rng=FixedRng(roll=15, choice_value="torso")) assert armored_result.armor_reduction == pytest.approx(3.0) assert armored_result.damage == pytest.approx(max(0.0, unarmored_result.damage - 3.0)) def test_heavy_armor_can_absorb_a_hit_entirely(registry): attacker = make_human(registry) defender = make_human(registry, clothing=[ClothingPiece("torso", "leather_jacket")]) # a roll that only barely clears the DC: margin (1) is smaller than armor_reduction (3) result = perform_attack(attacker, defender, registry, rng=FixedRng(roll=11, choice_value="torso")) assert result.hit is True # the check itself succeeded... assert result.damage == 0.0 # ...but armor soaked up all of it assert defender.get_body_part("torso").integrity == pytest.approx(100.0)