from pathlib import Path import pytest from engine.character import BodyPart, Character, ClothingPiece from engine.defs import DefRegistry 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", gender="female", default_body_parts=registry.new_default_body_parts("human"), **kwargs, ) def test_blood_status_thresholds(registry): species = registry.get_species_def("human") character = make_human(registry, blood_percentage=100.0) assert character.blood_status(species) == "normal" character.blood_percentage = 50.0 assert character.blood_status(species) == "danger" character.blood_percentage = 10.0 assert character.blood_status(species) == "critical" def test_bio_defaults_to_zeroed_dnd_skills(registry): character = make_human(registry) assert character.bio["stealth"] == 0 assert character.bio["athletics"] == 0 assert set(character.bio.keys()) >= {"perception", "insight", "acrobatics"} def test_mental_stats_optional_by_default(registry): character = make_human(registry) assert character.mental_stats is None npc = make_human(registry, mental_stats={"insanity": 0.0}) assert npc.mental_stats == {"insanity": 0.0} def test_skill_penalty_zero_when_all_parts_healthy(registry): character = make_human(registry) assert character.skill_penalty("acrobatics", registry) == 0.0 def test_skill_penalty_full_when_relevant_parts_missing(registry): character = make_human(registry) character.body_parts = [ BodyPart("left_leg", "human_left_leg", removed=True), BodyPart("right_leg", "human_right_leg", removed=True), ] # acrobatics is weighted only on the legs (0.5 each) -> fully lost when both are gone assert character.skill_penalty("acrobatics", registry) == pytest.approx(1.0) # athletics also draws from torso/arms, which are untouched -> partial loss penalty = character.skill_penalty("athletics", registry) assert 0.0 < penalty < 1.0 def test_skill_penalty_scales_with_integrity_not_just_removal(registry): character = make_human(registry) character.body_parts = [BodyPart("head", "human_head", integrity=50.0)] penalty = character.skill_penalty("perception", registry) assert penalty == pytest.approx(0.5) def test_get_body_part_returns_none_when_explicitly_removed(registry): character = make_human(registry) character.body_parts = [BodyPart("left_arm", "human_left_arm", removed=True)] assert character.get_body_part("left_arm") is None assert character.get_body_part("right_arm") is not None # still falls back to default def test_get_clothing_falls_back_to_default_like_body_parts(registry): character = make_human( registry, default_clothing=registry.new_default_clothing("player"), ) assert character.get_clothing("back").item_def_id == "backpack_basic" def test_get_clothing_override_beats_default_and_removed_returns_none(registry): character = make_human( registry, clothing=[ClothingPiece("torso", "leather_jacket")], default_clothing=registry.new_default_clothing("player"), ) assert character.get_clothing("torso").item_def_id == "leather_jacket" assert character.get_clothing("back").item_def_id == "backpack_basic" # still falls back character.clothing.append(ClothingPiece("back", "backpack_basic", removed=True)) assert character.get_clothing("back") is None def test_equipped_backpack_grants_a_lazily_created_inventory(registry): character = make_human( registry, default_clothing=registry.new_default_clothing("player"), ) assert character.get_clothing("back").inventory is None inventory = character.get_equipped_inventory("back", registry) assert (inventory.width, inventory.height) == (4, 4) # same instance on repeated access, and matches what's stored on the clothing piece assert character.get_equipped_inventory("back", registry) is inventory assert character.get_clothing("back").inventory is inventory def test_equipped_inventory_is_none_for_non_container_clothing(registry): character = make_human(registry, clothing=[ClothingPiece("torso", "leather_jacket")]) assert character.get_equipped_inventory("torso", registry) is None