185 lines
6.8 KiB
Python
185 lines
6.8 KiB
Python
import random
|
|
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.map_loader import MapLoader
|
|
from engine.tile import Tile, TileLayerInstance
|
|
from engine.world import World
|
|
from resources.logic.weather import (
|
|
HEATSTROKE_SLOT,
|
|
apply_heatstroke_tick,
|
|
apply_weather_tick,
|
|
find_random_exposed_position,
|
|
is_exposed,
|
|
strike_lightning,
|
|
)
|
|
|
|
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 load_storm_field(registry):
|
|
loader = MapLoader(DEFS_DIR / "maps")
|
|
return loader.load("storm_field.json")
|
|
|
|
|
|
# --- is_exposed --------------------------------------------------------------------------
|
|
|
|
|
|
def test_is_exposed_true_in_the_open(registry):
|
|
field = load_storm_field(registry)
|
|
assert is_exposed(field, 0, 0, 0, registry) is True
|
|
|
|
|
|
def test_is_exposed_false_under_the_shelter_roof(registry):
|
|
field = load_storm_field(registry)
|
|
assert is_exposed(field, 8, 8, 0, registry) is False
|
|
|
|
|
|
# --- heatstroke ----------------------------------------------------------------------------
|
|
|
|
|
|
def test_heatstroke_progresses_while_exposed(registry):
|
|
character = make_human(registry)
|
|
apply_heatstroke_tick(character, exposed=True, ticks=3)
|
|
ailment = next(a for a in character.get_body_part(HEATSTROKE_SLOT).ailments if a.id == "heatstroke")
|
|
assert ailment.progress == pytest.approx(15.0)
|
|
assert ailment.severity == pytest.approx(0.15)
|
|
|
|
|
|
def test_heatstroke_progress_caps_at_100(registry):
|
|
character = make_human(registry)
|
|
apply_heatstroke_tick(character, exposed=True, ticks=1000)
|
|
ailment = next(a for a in character.get_body_part(HEATSTROKE_SLOT).ailments if a.id == "heatstroke")
|
|
assert ailment.progress == 100.0
|
|
|
|
|
|
def test_heatstroke_recovers_while_sheltered(registry):
|
|
character = make_human(registry)
|
|
apply_heatstroke_tick(character, exposed=True, ticks=5)
|
|
apply_heatstroke_tick(character, exposed=False, ticks=2)
|
|
ailment = next(a for a in character.get_body_part(HEATSTROKE_SLOT).ailments if a.id == "heatstroke")
|
|
assert ailment.progress == pytest.approx(25.0 - 6.0)
|
|
|
|
|
|
def test_heatstroke_ailment_removed_once_fully_recovered(registry):
|
|
character = make_human(registry)
|
|
apply_heatstroke_tick(character, exposed=True, ticks=1)
|
|
apply_heatstroke_tick(character, exposed=False, ticks=10)
|
|
part = character.get_body_part(HEATSTROKE_SLOT)
|
|
assert not any(a.id == "heatstroke" for a in part.ailments)
|
|
|
|
|
|
def test_no_heatstroke_ailment_if_never_exposed(registry):
|
|
character = make_human(registry)
|
|
apply_heatstroke_tick(character, exposed=False, ticks=5)
|
|
part = character.get_body_part(HEATSTROKE_SLOT)
|
|
assert not any(a.id == "heatstroke" for a in part.ailments)
|
|
|
|
|
|
def test_heatstroke_zero_ticks_is_a_no_op(registry):
|
|
character = make_human(registry)
|
|
apply_heatstroke_tick(character, exposed=True, ticks=0)
|
|
part = character.get_body_part(HEATSTROKE_SLOT)
|
|
assert not any(a.id == "heatstroke" for a in part.ailments)
|
|
|
|
|
|
# --- lightning -----------------------------------------------------------------------------
|
|
|
|
|
|
def test_find_random_exposed_position_never_returns_the_sheltered_patch(registry):
|
|
field = load_storm_field(registry)
|
|
rng = random.Random(1234)
|
|
for _ in range(200):
|
|
pos = find_random_exposed_position(field, registry, rng)
|
|
assert pos is not None
|
|
assert is_exposed(field, *pos, registry) is True
|
|
|
|
|
|
def test_strike_lightning_returns_none_on_a_fully_roofed_map(registry):
|
|
roofed = Map("roofed", 3, 3, 1)
|
|
for x in range(3):
|
|
for y in range(3):
|
|
roofed.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("grass"), roof=TileLayerInstance("shingles")))
|
|
world = World(registry=registry)
|
|
world.add_map(roofed)
|
|
assert strike_lightning(world, "roofed", registry, random.Random(1)) is None
|
|
|
|
|
|
def test_strike_lightning_burns_about_half_the_body_parts_when_it_hits_someone(registry):
|
|
field = load_storm_field(registry)
|
|
world = World(registry=registry)
|
|
world.add_map(field)
|
|
|
|
rng = random.Random(42)
|
|
# find an exposed tile deterministically and place the victim exactly there
|
|
pos = find_random_exposed_position(field, registry, random.Random(42))
|
|
victim = make_human(registry, position=EntityPosition("storm_field", pos[0], pos[1], pos[2]))
|
|
world.add_entity(victim)
|
|
|
|
struck = strike_lightning(world, "storm_field", registry, rng)
|
|
assert struck == pos
|
|
|
|
burned_parts = [p for p in victim.body_parts if any(a.id == "burn" for a in p.ailments)]
|
|
assert 2 <= len(burned_parts) <= 4 # ~half of 6 default parts
|
|
for part in burned_parts:
|
|
assert part.integrity <= 40.0 # took "large" damage (started at 100, -60)
|
|
|
|
|
|
def test_strike_lightning_misses_if_nobody_is_on_the_struck_tile(registry):
|
|
field = load_storm_field(registry)
|
|
world = World(registry=registry)
|
|
world.add_map(field)
|
|
# no entities added at all
|
|
struck = strike_lightning(world, "storm_field", registry, random.Random(7))
|
|
assert struck is not None # still strikes a tile...
|
|
assert is_exposed(field, *struck, registry) is True
|
|
|
|
|
|
# --- apply_weather_tick orchestration --------------------------------------------------------
|
|
|
|
|
|
def test_apply_weather_tick_progresses_heatstroke_for_exposed_entities(registry):
|
|
field = load_storm_field(registry)
|
|
world = World(registry=registry)
|
|
world.add_map(field)
|
|
victim = make_human(registry, position=EntityPosition("storm_field", 0, 0, 0))
|
|
world.add_entity(victim)
|
|
|
|
apply_weather_tick(world, registry, "storm_field", ticks=2, rng=random.Random(1), lightning_chance_per_tick=0.0)
|
|
ailment = next(a for a in victim.get_body_part(HEATSTROKE_SLOT).ailments if a.id == "heatstroke")
|
|
assert ailment.progress == pytest.approx(10.0)
|
|
|
|
|
|
def test_apply_weather_tick_does_not_progress_heatstroke_under_shelter(registry):
|
|
field = load_storm_field(registry)
|
|
world = World(registry=registry)
|
|
world.add_map(field)
|
|
sheltered = make_human(registry, position=EntityPosition("storm_field", 8, 8, 0))
|
|
world.add_entity(sheltered)
|
|
|
|
apply_weather_tick(world, registry, "storm_field", ticks=3, rng=random.Random(1), lightning_chance_per_tick=0.0)
|
|
part = sheltered.get_body_part(HEATSTROKE_SLOT)
|
|
assert not any(a.id == "heatstroke" for a in part.ailments)
|
|
|
|
|
|
def test_apply_weather_tick_can_strike_lightning_with_high_chance(registry):
|
|
field = load_storm_field(registry)
|
|
world = World(registry=registry)
|
|
world.add_map(field)
|
|
struck = apply_weather_tick(world, registry, "storm_field", ticks=5, rng=random.Random(2), lightning_chance_per_tick=1.0)
|
|
assert struck is not None
|