80 lines
2.9 KiB
Python
80 lines
2.9 KiB
Python
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)
|