Mi2dRPGamEng/tests/test_weather.py

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