from pathlib import Path import pytest from engine.character import Character from engine.defs import DefRegistry from engine.entity import EntityPosition from engine.inventory import Inventory from engine.item import ItemInstance from engine.map import Map from engine.tile import Tile, TileLayerInstance from engine.world import World from resources.logic.actions import activate_hand from resources.logic.organs import apply_organ_interactions_tick DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs" @pytest.fixture() def registry(): return DefRegistry.load(DEFS_DIR) def make_character(registry, species_id: str, **kwargs) -> Character: return Character(species_id=species_id, default_body_parts=registry.new_default_body_parts(species_id), **kwargs) def make_world(registry): field = Map("field", 10, 10, 1) for x in range(10): for y in range(10): field.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("grass"))) world = World(registry=registry) world.add_map(field) return world, field # --- explicit, fully bespoke default_organs per species ------------------------------------- def test_human_species_gets_its_organs(registry): parts = registry.new_default_body_parts("human") torso = next(p for p in parts if p.slot == "torso") assert {o.organ_def_id for o in torso.organs} == {"human_heart", "human_lungs", "human_liver", "human_kidneys"} def test_hydraulibot_species_gets_bespoke_mechanical_organs(registry): parts = registry.new_default_body_parts("hydraulibot") head = next(p for p in parts if p.slot == "head") torso = next(p for p in parts if p.slot == "torso") assert {o.organ_def_id for o in head.organs} == {"hydraulibot_cpu", "hydraulibot_npu", "hydraulibot_camera_module", "hydraulibot_mic"} assert {o.organ_def_id for o in torso.organs} == { "hydraulibot_power_cell", "hydraulibot_hydraulic_fluid_bladder", "hydraulibot_cooling_fan", } def test_hydraulibot_power_cell_affects_multiple_organs(registry): hydraulibot = make_character(registry, "hydraulibot") torso = hydraulibot.get_or_create_body_part_override("torso") power_cell = next(o for o in torso.organs if o.organ_def_id == "hydraulibot_power_cell") power_cell.integrity = 0.0 apply_organ_interactions_tick(hydraulibot, registry, ticks=10) head = hydraulibot.get_body_part("head") cpu = next(o for o in head.organs if o.organ_def_id == "hydraulibot_cpu") npu = next(o for o in head.organs if o.organ_def_id == "hydraulibot_npu") fluid_bladder = next( o for o in hydraulibot.get_body_part("torso").organs if o.organ_def_id == "hydraulibot_hydraulic_fluid_bladder" ) assert cpu.integrity < 100.0 assert npu.integrity < 100.0 assert fluid_bladder.integrity < 100.0 def test_motorbot_species_gets_bespoke_mechanical_organs(registry): parts = registry.new_default_body_parts("motorbot") head = next(p for p in parts if p.slot == "head") torso = next(p for p in parts if p.slot == "torso") assert {o.organ_def_id for o in head.organs} == {"motorbot_cpu", "motorbot_npu", "motorbot_camera_lenses", "motorbot_mic"} assert {o.organ_def_id for o in torso.organs} == { "motorbot_battery", "motorbot_mechanical_transmission", "motorbot_main_motor", } def test_motorbot_battery_affects_main_motor(registry): motorbot = make_character(registry, "motorbot") torso = motorbot.get_or_create_body_part_override("torso") battery = next(o for o in torso.organs if o.organ_def_id == "motorbot_battery") battery.integrity = 0.0 apply_organ_interactions_tick(motorbot, registry, ticks=10) main_motor = next(o for o in motorbot.get_body_part("torso").organs if o.organ_def_id == "motorbot_main_motor") assert main_motor.integrity < 100.0 def test_reptilian_venom_gland_is_bespoke_not_shared_with_other_species(registry): parts = registry.new_default_body_parts("reptilian_quad") torso = next(p for p in parts if p.slot == "torso") assert {o.organ_def_id for o in torso.organs} == { "reptilian_heart", "reptilian_lung", "reptilian_liver", "reptilian_venom_gland", } def test_zenari_organs_are_bespoke(registry): parts = registry.new_default_body_parts("alien_tri") torso = next(p for p in parts if p.slot == "torso") head = next(p for p in parts if p.slot == "head") assert {o.organ_def_id for o in torso.organs} == { "zenari_cardial_node", "zenari_respirator", "zenari_metabolizer", "zenari_filtration_organ", } assert {o.organ_def_id for o in head.organs} == {"zenari_cortex", "zenari_eyes", "zenari_ears"} def test_dog_organs_are_bespoke_not_shared_with_human(registry): parts = registry.new_default_body_parts("dog") torso = next(p for p in parts if p.slot == "torso") assert {o.organ_def_id for o in torso.organs} == {"dog_heart", "dog_lungs", "dog_liver", "dog_kidneys"} def test_no_species_shares_an_organ_id_with_another(registry): species_ids = ("human", "reptilian_quad", "alien_tri", "hydraulibot", "motorbot", "dog") seen: dict[str, str] = {} for species_id in species_ids: species = registry.get_species_def(species_id) for organ_id in species.default_organs: assert organ_id not in seen, f"{organ_id!r} shared between {seen.get(organ_id)!r} and {species_id!r}" seen[organ_id] = species_id # --- reptilian_quad: 4 arm slots ------------------------------------------------------------- def test_reptilian_has_four_arm_slots(registry): species = registry.get_species_def("reptilian_quad") slots = {p.slot for p in species.default_body_parts} assert {"left_arm", "right_arm", "left_arm_2", "right_arm_2"} <= slots def test_reptilian_can_wield_two_two_handed_tools_at_once(registry): reptilian = make_character(registry, "reptilian_quad") assert reptilian.wield_item("right_hand", "multi_deconstructor", registry) is True assert reptilian.wield_item("right_hand_2", "constructor_tool", registry) is True assert reptilian.get_held_item("left_hand").item_def_id == "multi_deconstructor" assert reptilian.get_held_item("left_hand_2").item_def_id == "constructor_tool" def test_reptilian_secondary_hand_pair_does_not_disturb_primary(registry): reptilian = make_character(registry, "reptilian_quad") reptilian.wield_item("right_hand", "staff_oak", registry) reptilian.wield_item("right_hand_2", "multi_deconstructor", registry) assert reptilian.get_held_item("right_hand").item_def_id == "staff_oak" assert reptilian.get_held_item("left_hand") is None # staff is one-handed, doesn't touch left_hand assert reptilian.get_held_item("left_hand_2").item_def_id == "multi_deconstructor" def test_two_armed_alien_cannot_use_secondary_hand_pair(registry): alien = make_character(registry, "alien_tri") assert alien.wield_item("right_hand_2", "staff_oak", registry) is False # --- alien_tri: 3 genders -------------------------------------------------------------------- def test_alien_tri_species_declares_three_genders(registry): species = registry.get_species_def("alien_tri") assert species.genders == ["alpha", "beta", "gamma"] # --- dog: quadruped, no arms ----------------------------------------------------------------- def test_dog_has_no_arm_slots(registry): species = registry.get_species_def("dog") slots = {p.slot for p in species.default_body_parts} assert slots == {"head", "torso", "front_left_leg", "front_right_leg", "back_left_leg", "back_right_leg"} def test_dog_cannot_wield_anything(registry): dog = make_character(registry, "dog") assert dog.wield_item("right_hand", "staff_oak", registry) is False assert dog.held_items == [] def test_dog_effective_speed_uses_all_four_legs(registry): dog = make_character(registry, "dog") human = make_character(registry, "human") # 4 legs at 0.25 speed_factor each should sum to the same total factor as 2 legs at 0.5 each assert dog.effective_speed(30.0, registry) == pytest.approx(human.effective_speed(30.0, registry)) def test_dog_can_perform_bite_attack_but_not_arm_dependent_abilities(registry): dog = make_character(registry, "dog") assert dog.can_perform_ability("melee_attack", registry) is True assert dog.can_perform_ability("time_warp_sphere", registry) is False # not in dog's ability list at all # --- one-ability-per-required-hand: de/constructor tools, verified across species ----------- def test_constructor_via_left_hand_also_builds(registry): world, field = make_world(registry) character = make_character(registry, "human", position=EntityPosition("field", 5, 5, 0)) character.wield_item("left_hand", "constructor_tool", registry) character.inventory = Inventory(4, 4) planks_def = registry.get_item_def("wood_planks") character.inventory.place(ItemInstance("planks-1", "wood_planks"), planks_def, 0, 0) world.add_entity(character) result = activate_hand(character, world, registry, "left_hand", (1, 0, 0)) assert result is True assert field.get_tile(6, 5, 0).room.def_id == "wood_wall" def test_reptilian_secondary_pair_deconstructor_independent_of_primary_pair_tool(registry): """A 4-armed species can wield a 2-handed tool in each hand pair; activating one hand pair's tool must not affect the other pair's separately-wielded tool - each required hand only ever triggers the ability of whatever it itself is holding. """ world, field = make_world(registry) field.get_tile(6, 5, 0).room = TileLayerInstance("wood_wall") reptilian = make_character(registry, "reptilian_quad", position=EntityPosition("field", 5, 5, 0)) reptilian.wield_item("right_hand", "multi_deconstructor", registry) reptilian.wield_item("right_hand_2", "constructor_tool", registry) world.add_entity(reptilian) # right_hand's deconstructor: left-click semantics (button="left") -> destroy, no item yielded result = activate_hand(reptilian, world, registry, "right_hand", (1, 0, 0)) assert result is True assert field.get_tile(6, 5, 0).room is None # the secondary pair's constructor is untouched by the above and still independently usable assert reptilian.get_held_item("left_hand_2").item_def_id == "constructor_tool"