Mi2dRPGamEng/tests/test_combat_ailments.py

211 lines
7.5 KiB
Python

from pathlib import Path
import pytest
from engine.character import Ailment, BodyPart, Character
from engine.defs import DefRegistry
from resources.logic.combat import (
FRACTURE_ROLL_THRESHOLD,
IMPACT_TRAUMA_ROLL_THRESHOLD,
apply_combat_ailment,
combat_ailment_for_roll,
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):
self._roll = roll
def randint(self, _lo, _hi):
return self._roll
def choice(self, seq):
return seq[0]
# --- combat_ailment_for_roll: pure roll -> ailment tiers -------------------------------------
def test_low_roll_causes_no_ailment():
assert combat_ailment_for_roll(IMPACT_TRAUMA_ROLL_THRESHOLD - 1, "torso") is None
def test_solid_roll_causes_impact_trauma():
assert combat_ailment_for_roll(IMPACT_TRAUMA_ROLL_THRESHOLD, "torso") == "impact_trauma"
assert combat_ailment_for_roll(FRACTURE_ROLL_THRESHOLD - 1, "left_arm") == "impact_trauma"
def test_near_perfect_roll_causes_a_fracture_on_a_non_head_slot():
assert combat_ailment_for_roll(FRACTURE_ROLL_THRESHOLD, "left_arm") == "fracture"
assert combat_ailment_for_roll(20, "torso") == "fracture"
def test_near_perfect_roll_on_the_head_causes_brain_trauma_instead():
assert combat_ailment_for_roll(FRACTURE_ROLL_THRESHOLD, "head") == "brain_trauma"
assert combat_ailment_for_roll(20, "head") == "brain_trauma"
# --- apply_combat_ailment: add vs. refresh -----------------------------------------------------
def test_apply_combat_ailment_adds_a_new_ailment():
part = BodyPart("torso", "human_torso")
ailment = apply_combat_ailment(part, "fracture")
assert part.ailments == [ailment]
assert ailment.id == "fracture"
assert ailment.severity == pytest.approx(0.8)
def test_apply_combat_ailment_does_not_duplicate_the_same_ailment():
part = BodyPart("torso", "human_torso")
apply_combat_ailment(part, "impact_trauma")
apply_combat_ailment(part, "impact_trauma")
assert len(part.ailments) == 1
def test_apply_combat_ailment_upgrades_severity_but_never_downgrades():
part = BodyPart("torso", "human_torso")
part.ailments.append(Ailment(id="fracture", name="Fracture", severity=0.2)) # e.g. healing
apply_combat_ailment(part, "fracture")
assert part.ailments[0].severity == pytest.approx(0.8) # took the worse (combat) severity
# --- integration: perform_attack actually inflicts ailments on a hit ---------------------------
def test_low_precision_hit_damages_but_inflicts_no_ailment(registry):
attacker = make_human(registry)
attacker.bio["athletics"] = 10
defender = make_human(registry)
result = perform_attack(attacker, defender, registry, target_slot="torso", rng=FixedRng(roll=12))
assert result.hit is True
torso = defender.get_body_part("torso")
assert torso.integrity < 100.0
assert torso.ailments == []
def test_solid_hit_inflicts_impact_trauma(registry):
attacker = make_human(registry)
attacker.bio["athletics"] = 10
defender = make_human(registry)
result = perform_attack(attacker, defender, registry, target_slot="torso", rng=FixedRng(roll=IMPACT_TRAUMA_ROLL_THRESHOLD))
assert result.hit is True
torso = defender.get_body_part("torso")
assert [a.id for a in torso.ailments] == ["impact_trauma"]
def test_near_perfect_hit_inflicts_a_fracture(registry):
attacker = make_human(registry)
attacker.bio["athletics"] = 10
defender = make_human(registry)
result = perform_attack(attacker, defender, registry, target_slot="left_arm", rng=FixedRng(roll=FRACTURE_ROLL_THRESHOLD))
assert result.hit is True
arm = defender.get_body_part("left_arm")
assert [a.id for a in arm.ailments] == ["fracture"]
def test_near_perfect_hit_on_the_head_inflicts_brain_trauma(registry):
attacker = make_human(registry)
attacker.bio["athletics"] = 10
defender = make_human(registry)
result = perform_attack(attacker, defender, registry, target_slot="head", rng=FixedRng(roll=20))
assert result.hit is True
head = defender.get_body_part("head")
assert [a.id for a in head.ailments] == ["brain_trauma"]
def test_a_miss_inflicts_no_ailment(registry):
attacker = make_human(registry) # athletics 0
defender = make_human(registry)
result = perform_attack(attacker, defender, registry, target_slot="head", rng=FixedRng(roll=1))
assert result.hit is False
assert defender.get_body_part("head").ailments == []
# --- ailment_penalty / total_check_penalty ------------------------------------------------------
def test_ailment_penalty_zero_when_no_ailments(registry):
character = make_human(registry)
assert character.ailment_penalty("strength", registry) == 0.0
def test_ailment_penalty_zero_for_an_ailment_with_no_matching_def(registry):
character = make_human(registry)
torso = character.get_or_create_body_part_override("torso")
torso.ailments.append(Ailment(id="frostbite", name="Frostbite", severity=1.0)) # no AilmentDef exists for this
assert character.ailment_penalty("strength", registry) == 0.0
def test_ailment_penalty_rises_with_a_fracture(registry):
character = make_human(registry)
arm = character.get_or_create_body_part_override("left_arm")
apply_combat_ailment(arm, "fracture")
penalty = character.ailment_penalty("strength", registry)
assert penalty > 0.0
def test_ailment_penalty_zero_for_an_unrelated_check(registry):
character = make_human(registry)
torso = character.get_or_create_body_part_override("torso")
apply_combat_ailment(torso, "fracture") # fracture doesn't weight "insight"
assert character.ailment_penalty("insight", registry) == 0.0
def test_brain_trauma_hits_insight_and_perception_hard(registry):
character = make_human(registry)
head = character.get_or_create_body_part_override("head")
apply_combat_ailment(head, "brain_trauma")
assert character.ailment_penalty("insight", registry) == pytest.approx(0.9)
def test_total_check_penalty_combines_skill_organ_and_ailment_damage(registry):
character = make_human(registry)
character.body_parts = [BodyPart("left_arm", "human_left_arm", integrity=60.0)]
arm = character.get_or_create_body_part_override("left_arm")
apply_combat_ailment(arm, "impact_trauma")
combined = character.total_check_penalty("athletics", registry)
body_only = character.skill_penalty("athletics", registry)
ailment_only = character.ailment_penalty("athletics", registry)
assert combined > body_only
assert combined > ailment_only
assert combined <= 1.0
def test_total_check_penalty_unaffected_when_no_ailments_present(registry):
character = make_human(registry)
character.body_parts = [BodyPart("left_leg", "human_left_leg", integrity=20.0)]
assert character.total_check_penalty("acrobatics", registry) == pytest.approx(
character.skill_penalty("acrobatics", registry)
)
# --- registry loading -----------------------------------------------------------------------
def test_ailment_defs_load_with_check_weights(registry):
fracture = registry.get_ailment_def("fracture")
assert fracture is not None
assert fracture.check_weights["strength"] == pytest.approx(0.5)
def test_get_ailment_def_returns_none_for_unknown_id(registry):
assert registry.get_ailment_def("not_a_real_ailment") is None