from pathlib import Path import pytest from engine.character import Character from engine.defs import DefRegistry from engine.entity import EntityPosition from engine.map import Map from engine.tile import Tile, TileLayerInstance from engine.ui import AbilityBarSlot from engine.world import World from resources.logic.actions import ABILITY_COOLDOWNS, activate_ability_bar_slot, activate_hand, activate_implant 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) 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 # --- Character.is_ability_ready / start_ability_cooldown ------------------------------------ def test_ability_ready_by_default(registry): character = make_human(registry) assert character.is_ability_ready("time_warp_sphere", now=0.0) is True def test_start_ability_cooldown_blocks_until_it_expires(registry): character = make_human(registry) character.start_ability_cooldown("time_warp_sphere", now=10.0, duration=5.0) assert character.is_ability_ready("time_warp_sphere", now=12.0) is False assert character.is_ability_ready("time_warp_sphere", now=15.0) is True def test_zero_duration_cooldown_is_a_no_op(registry): character = make_human(registry) character.start_ability_cooldown("time_warp_sphere", now=10.0, duration=0.0) assert character.is_ability_ready("time_warp_sphere", now=10.0) is True # --- activate_hand (chronowand) respects cooldown -------------------------------------------- def test_chronowand_cast_starts_a_cooldown(registry): world = make_world(registry) caster = make_human(registry, position=EntityPosition("field", 5, 5, 0)) caster.wield_item("right_hand", "chronowand", registry) world.add_entity(caster) activate_hand(caster, world, registry, "right_hand", (1, 0, 0), now=0.0) assert caster.is_ability_ready("time_warp_sphere", now=0.0) is False def test_chronowand_cannot_recast_while_on_cooldown(registry): world = make_world(registry) caster = make_human(registry, position=EntityPosition("field", 5, 5, 0)) caster.wield_item("right_hand", "chronowand", registry) world.add_entity(caster) activate_hand(caster, world, registry, "right_hand", (1, 0, 0), now=0.0) result = activate_hand(caster, world, registry, "right_hand", (1, 0, 0), now=1.0) assert result is None assert len(world.time_warp_zones) == 1 # still just the first cast def test_chronowand_can_recast_after_cooldown_expires(registry): world = make_world(registry) caster = make_human(registry, position=EntityPosition("field", 5, 5, 0)) caster.wield_item("right_hand", "chronowand", registry) world.add_entity(caster) activate_hand(caster, world, registry, "right_hand", (1, 0, 0), now=0.0) cooldown = ABILITY_COOLDOWNS["time_warp_sphere"] result = activate_hand(caster, world, registry, "right_hand", (1, 0, 0), now=cooldown + 0.1) assert result is not None assert len(world.time_warp_zones) == 2 # --- activate_implant respects cooldown ------------------------------------------------------- def test_implant_cannot_recast_while_on_cooldown(registry): world = make_world(registry) caster = make_human(registry, position=EntityPosition("field", 5, 5, 0)) caster.install_implant("chronoimplant", registry) world.add_entity(caster) activate_implant(caster, world, registry, "chronoimplant", now=0.0) result = activate_implant(caster, world, registry, "chronoimplant", now=1.0) assert result is None assert len(world.time_warp_zones) == 1 # --- cooldown is shared across activation paths (hand, implant, ability bar) ------------------ def test_cooldown_is_shared_between_hand_and_implant_paths(registry): world = make_world(registry) caster = make_human(registry, position=EntityPosition("field", 5, 5, 0)) caster.wield_item("right_hand", "chronowand", registry) caster.install_implant("chronoimplant", registry) world.add_entity(caster) activate_hand(caster, world, registry, "right_hand", (1, 0, 0), now=0.0) # same underlying ability (time_warp_sphere), so the implant is also on cooldown now result = activate_implant(caster, world, registry, "chronoimplant", now=1.0) assert result is None assert len(world.time_warp_zones) == 1 def test_ability_bar_slot_respects_cooldown_too(registry): world = make_world(registry) caster = make_human(registry, position=EntityPosition("field", 5, 5, 0)) caster.install_implant("chronoimplant", registry) world.add_entity(caster) slot = AbilityBarSlot(kind="implant", source_id="chronoimplant", ability_id="time_warp_sphere") activate_ability_bar_slot(caster, world, registry, slot, (1, 0, 0), now=0.0) result = activate_ability_bar_slot(caster, world, registry, slot, (1, 0, 0), now=1.0) assert result is None assert len(world.time_warp_zones) == 1