192 lines
7.5 KiB
Python
192 lines
7.5 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from engine.character import BodyPart, Character
|
|
from engine.defs import DefRegistry
|
|
from engine.entity import EntityPosition
|
|
from engine.map import Map
|
|
from engine.tile import Tile, TileLayerInstance
|
|
from engine.world import TimeWarpZone, World
|
|
from resources.logic.abilities import cast_time_warp_sphere
|
|
from resources.logic.actions import activate_hand
|
|
|
|
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", 15, 15, 1)
|
|
for x in range(15):
|
|
for y in range(15):
|
|
field.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("grass")))
|
|
world = World(registry=registry)
|
|
world.add_map(field)
|
|
return world
|
|
|
|
|
|
# --- TimeWarpZone / World plumbing --------------------------------------------------------
|
|
|
|
|
|
def test_zone_contains_checks_radius_and_map_and_layer():
|
|
zone = TimeWarpZone(map_id="field", center=(5.0, 5.0, 0.0), radius=2.0, speed_factor=0.3, expires_at=10.0)
|
|
assert zone.contains("field", 6.0, 5.0, 0.0) is True
|
|
assert zone.contains("field", 8.0, 5.0, 0.0) is False # outside radius
|
|
assert zone.contains("other_map", 6.0, 5.0, 0.0) is False
|
|
assert zone.contains("field", 6.0, 5.0, 1.0) is False # different layer
|
|
|
|
|
|
def test_zone_affects_respects_whitelist():
|
|
zone = TimeWarpZone(
|
|
map_id="field", center=(5.0, 5.0, 0.0), radius=2.0, speed_factor=0.3, expires_at=10.0,
|
|
immune_entity_ids={"caster-1"},
|
|
)
|
|
assert zone.affects("caster-1") is False
|
|
assert zone.affects("anyone-else") is True
|
|
|
|
|
|
def test_speed_multiplier_at_applies_zone_factor_inside_radius(registry):
|
|
world = make_world(registry)
|
|
world.time_warp_zones.append(
|
|
TimeWarpZone(map_id="field", center=(5.0, 5.0, 0.0), radius=2.0, speed_factor=0.25, expires_at=10.0)
|
|
)
|
|
assert world.speed_multiplier_at("someone", "field", 5.5, 5.0, 0.0, now=1.0) == pytest.approx(0.25)
|
|
|
|
|
|
def test_speed_multiplier_at_is_1_outside_radius(registry):
|
|
world = make_world(registry)
|
|
world.time_warp_zones.append(
|
|
TimeWarpZone(map_id="field", center=(5.0, 5.0, 0.0), radius=2.0, speed_factor=0.25, expires_at=10.0)
|
|
)
|
|
assert world.speed_multiplier_at("someone", "field", 10.0, 5.0, 0.0, now=1.0) == 1.0
|
|
|
|
|
|
def test_speed_multiplier_at_ignores_expired_zones(registry):
|
|
world = make_world(registry)
|
|
world.time_warp_zones.append(
|
|
TimeWarpZone(map_id="field", center=(5.0, 5.0, 0.0), radius=2.0, speed_factor=0.25, expires_at=10.0)
|
|
)
|
|
assert world.speed_multiplier_at("someone", "field", 5.5, 5.0, 0.0, now=11.0) == 1.0
|
|
|
|
|
|
def test_speed_multiplier_at_1_for_whitelisted_entity(registry):
|
|
world = make_world(registry)
|
|
world.time_warp_zones.append(
|
|
TimeWarpZone(
|
|
map_id="field", center=(5.0, 5.0, 0.0), radius=2.0, speed_factor=0.25, expires_at=10.0,
|
|
immune_entity_ids={"caster-1"},
|
|
)
|
|
)
|
|
assert world.speed_multiplier_at("caster-1", "field", 5.5, 5.0, 0.0, now=1.0) == 1.0
|
|
|
|
|
|
def test_multiple_overlapping_zones_multiply_together(registry):
|
|
world = make_world(registry)
|
|
world.time_warp_zones.append(
|
|
TimeWarpZone(map_id="field", center=(5.0, 5.0, 0.0), radius=3.0, speed_factor=0.5, expires_at=10.0)
|
|
)
|
|
world.time_warp_zones.append(
|
|
TimeWarpZone(map_id="field", center=(5.0, 5.0, 0.0), radius=3.0, speed_factor=0.5, expires_at=10.0)
|
|
)
|
|
assert world.speed_multiplier_at("someone", "field", 5.0, 5.0, 0.0, now=1.0) == pytest.approx(0.25)
|
|
|
|
|
|
def test_expire_time_warp_zones_removes_only_expired():
|
|
world = World()
|
|
active = TimeWarpZone(map_id="field", center=(0, 0, 0), radius=1, speed_factor=0.5, expires_at=10.0)
|
|
expired = TimeWarpZone(map_id="field", center=(0, 0, 0), radius=1, speed_factor=0.5, expires_at=5.0)
|
|
world.time_warp_zones = [active, expired]
|
|
world.expire_time_warp_zones(now=6.0)
|
|
assert world.time_warp_zones == [active]
|
|
|
|
|
|
# --- cast_time_warp_sphere ------------------------------------------------------------------
|
|
|
|
|
|
def test_cast_time_warp_sphere_defaults_center_to_caster_position(registry):
|
|
world = make_world(registry)
|
|
caster = make_human(registry, position=EntityPosition("field", 4.0, 6.0, 0.0))
|
|
zone = cast_time_warp_sphere(caster, world, now=0.0)
|
|
assert zone.center == (4.0, 6.0, 0.0)
|
|
assert zone in world.time_warp_zones
|
|
|
|
|
|
def test_cast_time_warp_sphere_caster_is_always_immune(registry):
|
|
world = make_world(registry)
|
|
caster = make_human(registry, position=EntityPosition("field", 4.0, 6.0, 0.0))
|
|
zone = cast_time_warp_sphere(caster, world, now=0.0)
|
|
assert caster.entity_id in zone.immune_entity_ids
|
|
|
|
|
|
def test_cast_time_warp_sphere_respects_explicit_whitelist(registry):
|
|
world = make_world(registry)
|
|
caster = make_human(registry, position=EntityPosition("field", 4.0, 6.0, 0.0))
|
|
zone = cast_time_warp_sphere(caster, world, now=0.0, whitelist={"ally-1"})
|
|
assert "ally-1" in zone.immune_entity_ids
|
|
assert caster.entity_id in zone.immune_entity_ids # still auto-added
|
|
|
|
|
|
def test_cast_time_warp_sphere_all_parameters_are_configurable(registry):
|
|
world = make_world(registry)
|
|
caster = make_human(registry, position=EntityPosition("field", 4.0, 6.0, 0.0))
|
|
zone = cast_time_warp_sphere(
|
|
caster, world, now=2.0, center=(1.0, 1.0, 0.0), radius=7.0, speed_factor=0.1, duration=12.0
|
|
)
|
|
assert zone.center == (1.0, 1.0, 0.0)
|
|
assert zone.radius == 7.0
|
|
assert zone.speed_factor == pytest.approx(0.1)
|
|
assert zone.expires_at == pytest.approx(14.0)
|
|
|
|
|
|
def test_time_warp_sphere_slows_a_non_whitelisted_entity_but_not_the_caster(registry):
|
|
world = make_world(registry)
|
|
caster = make_human(registry, position=EntityPosition("field", 5.0, 5.0, 0.0))
|
|
victim = make_human(registry, position=EntityPosition("field", 6.0, 5.0, 0.0))
|
|
world.add_entity(caster)
|
|
world.add_entity(victim)
|
|
|
|
cast_time_warp_sphere(caster, world, now=0.0, radius=3.0, speed_factor=0.2, duration=5.0)
|
|
|
|
caster_mult = world.speed_multiplier_at(caster.entity_id, "field", 5.0, 5.0, 0.0, now=1.0)
|
|
victim_mult = world.speed_multiplier_at(victim.entity_id, "field", 6.0, 5.0, 0.0, now=1.0)
|
|
assert caster_mult == 1.0
|
|
assert victim_mult == pytest.approx(0.2)
|
|
|
|
|
|
# --- item attachment point (chronowand) via activate_hand -----------------------------------
|
|
|
|
|
|
def test_chronowand_casts_time_warp_sphere_via_activate_hand(registry):
|
|
world = make_world(registry)
|
|
caster = make_human(registry, position=EntityPosition("field", 5.0, 5.0, 0.0))
|
|
caster.wield_item("right_hand", "chronowand", registry)
|
|
world.add_entity(caster)
|
|
|
|
assert world.time_warp_zones == []
|
|
result = activate_hand(caster, world, registry, "right_hand", (1, 0, 0), now=3.0)
|
|
assert len(world.time_warp_zones) == 1
|
|
assert result is world.time_warp_zones[0]
|
|
assert result.expires_at > 3.0
|
|
|
|
|
|
def test_species_def_carries_time_warp_sphere_ability(registry):
|
|
species = registry.get_species_def("human")
|
|
ability = next(a for a in species.abilities if a.id == "time_warp_sphere")
|
|
assert ability.required_slots == ("head",)
|
|
|
|
|
|
def test_can_perform_ability_gates_time_warp_sphere_on_head(registry):
|
|
intact = make_human(registry)
|
|
assert intact.can_perform_ability("time_warp_sphere", registry) is True
|
|
|
|
headless = make_human(registry, body_parts=[BodyPart("head", "human_head", removed=True)])
|
|
assert headless.can_perform_ability("time_warp_sphere", registry) is False
|