Add mob home regions to spawn rules and entity
This commit is contained in:
parent
755d23aa13
commit
5e0ec120c6
5 changed files with 234 additions and 1 deletions
|
|
@ -76,3 +76,7 @@ class Mob(Entity):
|
|||
alive: bool = True
|
||||
moves: list[str] = field(default_factory=list)
|
||||
next_action_at: float = 0.0
|
||||
home_x_min: int | None = None
|
||||
home_x_max: int | None = None
|
||||
home_y_min: int | None = None
|
||||
home_y_max: int | None = None
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ def load_mob_templates(directory: Path) -> dict[str, MobTemplate]:
|
|||
return templates
|
||||
|
||||
|
||||
def spawn_mob(template: MobTemplate, x: int, y: int, zone: Zone) -> Mob:
|
||||
def spawn_mob(
|
||||
template: MobTemplate, x: int, y: int, zone: Zone, home_region: dict | None = None
|
||||
) -> Mob:
|
||||
"""Create a Mob instance from a template at the given position.
|
||||
|
||||
Args:
|
||||
|
|
@ -72,6 +74,7 @@ def spawn_mob(template: MobTemplate, x: int, y: int, zone: Zone) -> Mob:
|
|||
x: X coordinate in the zone
|
||||
y: Y coordinate in the zone
|
||||
zone: The zone where the mob will be spawned
|
||||
home_region: Optional home region dict with x and y bounds
|
||||
|
||||
Returns:
|
||||
The spawned Mob instance
|
||||
|
|
@ -87,6 +90,11 @@ def spawn_mob(template: MobTemplate, x: int, y: int, zone: Zone) -> Mob:
|
|||
description=template.description,
|
||||
moves=list(template.moves),
|
||||
)
|
||||
if home_region is not None:
|
||||
mob.home_x_min = home_region["x"][0]
|
||||
mob.home_x_max = home_region["x"][1]
|
||||
mob.home_y_min = home_region["y"][0]
|
||||
mob.home_y_max = home_region["y"][1]
|
||||
mobs.append(mob)
|
||||
return mob
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class SpawnRule:
|
|||
mob: str
|
||||
max_count: int = 1
|
||||
respawn_seconds: int = 300
|
||||
home_region: dict | None = None
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ def load_zone(path: Path) -> Zone:
|
|||
mob=spawn["mob"],
|
||||
max_count=spawn.get("max_count", 1),
|
||||
respawn_seconds=spawn.get("respawn_seconds", 300),
|
||||
home_region=spawn.get("home_region"),
|
||||
)
|
||||
for spawn in spawns_data
|
||||
]
|
||||
|
|
|
|||
219
tests/test_mob_home_region.py
Normal file
219
tests/test_mob_home_region.py
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
"""Tests for mob home region system."""
|
||||
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
from mudlib.entity import Mob
|
||||
from mudlib.mobs import MobTemplate, mobs, spawn_mob
|
||||
from mudlib.zone import SpawnRule, Zone
|
||||
from mudlib.zones import load_zone
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_mobs():
|
||||
mobs.clear()
|
||||
yield
|
||||
mobs.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def zone():
|
||||
terrain = [["." for _ in range(20)] for _ in range(20)]
|
||||
return Zone(
|
||||
name="forest",
|
||||
width=20,
|
||||
height=20,
|
||||
terrain=terrain,
|
||||
toroidal=False,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def template():
|
||||
return MobTemplate(
|
||||
name="squirrel",
|
||||
description="a bushy-tailed squirrel",
|
||||
pl=10,
|
||||
stamina=20,
|
||||
max_stamina=20,
|
||||
moves=[],
|
||||
)
|
||||
|
||||
|
||||
# --- SpawnRule ---
|
||||
|
||||
|
||||
def test_spawn_rule_default_no_home_region():
|
||||
"""SpawnRule has no home_region by default."""
|
||||
rule = SpawnRule(mob="goblin")
|
||||
assert rule.home_region is None
|
||||
|
||||
|
||||
def test_spawn_rule_with_home_region():
|
||||
"""SpawnRule can have a home_region."""
|
||||
rule = SpawnRule(
|
||||
mob="goblin",
|
||||
home_region={"x": [5, 15], "y": [3, 10]},
|
||||
)
|
||||
assert rule.home_region == {"x": [5, 15], "y": [3, 10]}
|
||||
|
||||
|
||||
# --- Mob fields ---
|
||||
|
||||
|
||||
def test_mob_home_region_defaults():
|
||||
"""Mob has no home region by default."""
|
||||
mob = Mob(name="rat", x=0, y=0)
|
||||
assert mob.home_x_min is None
|
||||
assert mob.home_x_max is None
|
||||
assert mob.home_y_min is None
|
||||
assert mob.home_y_max is None
|
||||
|
||||
|
||||
def test_mob_home_region_set():
|
||||
"""Mob can have home region bounds."""
|
||||
mob = Mob(
|
||||
name="rat",
|
||||
x=5,
|
||||
y=5,
|
||||
home_x_min=3,
|
||||
home_x_max=10,
|
||||
home_y_min=2,
|
||||
home_y_max=8,
|
||||
)
|
||||
assert mob.home_x_min == 3
|
||||
assert mob.home_x_max == 10
|
||||
assert mob.home_y_min == 2
|
||||
assert mob.home_y_max == 8
|
||||
|
||||
|
||||
def test_mob_in_home_region():
|
||||
"""Mob at position within home region."""
|
||||
mob = Mob(
|
||||
name="rat",
|
||||
x=5,
|
||||
y=5,
|
||||
home_x_min=3,
|
||||
home_x_max=10,
|
||||
home_y_min=2,
|
||||
home_y_max=8,
|
||||
)
|
||||
assert mob.home_x_min is not None
|
||||
assert mob.home_x_max is not None
|
||||
assert mob.home_y_min is not None
|
||||
assert mob.home_y_max is not None
|
||||
assert mob.home_x_min <= mob.x <= mob.home_x_max
|
||||
assert mob.home_y_min <= mob.y <= mob.home_y_max
|
||||
|
||||
|
||||
# --- spawn_mob with home region ---
|
||||
|
||||
|
||||
def test_spawn_mob_with_home_region(zone, template):
|
||||
"""spawn_mob sets home region from SpawnRule."""
|
||||
rule = SpawnRule(
|
||||
mob="squirrel",
|
||||
home_region={"x": [5, 15], "y": [3, 10]},
|
||||
)
|
||||
mob = spawn_mob(template, 10, 7, zone, home_region=rule.home_region)
|
||||
assert mob.home_x_min == 5
|
||||
assert mob.home_x_max == 15
|
||||
assert mob.home_y_min == 3
|
||||
assert mob.home_y_max == 10
|
||||
|
||||
|
||||
def test_spawn_mob_without_home_region(zone, template):
|
||||
"""spawn_mob without home_region leaves bounds as None."""
|
||||
mob = spawn_mob(template, 10, 7, zone)
|
||||
assert mob.home_x_min is None
|
||||
assert mob.home_x_max is None
|
||||
|
||||
|
||||
# --- TOML loading ---
|
||||
|
||||
|
||||
def test_load_zone_spawn_with_home_region():
|
||||
"""Zone TOML with home_region on spawn rule."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
|
||||
f.write(
|
||||
"""
|
||||
name = "test_zone"
|
||||
width = 20
|
||||
height = 20
|
||||
|
||||
[terrain]
|
||||
rows = [
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
"....................",
|
||||
]
|
||||
|
||||
[[spawns]]
|
||||
mob = "squirrel"
|
||||
max_count = 2
|
||||
respawn_seconds = 180
|
||||
home_region = { x = [5, 15], y = [3, 10] }
|
||||
"""
|
||||
)
|
||||
temp_path = pathlib.Path(f.name)
|
||||
|
||||
try:
|
||||
zone = load_zone(temp_path)
|
||||
assert len(zone.spawn_rules) == 1
|
||||
rule = zone.spawn_rules[0]
|
||||
assert rule.home_region == {"x": [5, 15], "y": [3, 10]}
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
|
||||
def test_load_zone_spawn_without_home_region():
|
||||
"""Zone TOML without home_region on spawn rule."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
|
||||
f.write(
|
||||
"""
|
||||
name = "test_zone"
|
||||
width = 5
|
||||
height = 5
|
||||
|
||||
[terrain]
|
||||
rows = [
|
||||
".....",
|
||||
".....",
|
||||
".....",
|
||||
".....",
|
||||
".....",
|
||||
]
|
||||
|
||||
[[spawns]]
|
||||
mob = "goblin"
|
||||
max_count = 1
|
||||
"""
|
||||
)
|
||||
temp_path = pathlib.Path(f.name)
|
||||
|
||||
try:
|
||||
zone = load_zone(temp_path)
|
||||
assert len(zone.spawn_rules) == 1
|
||||
rule = zone.spawn_rules[0]
|
||||
assert rule.home_region is None
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
Loading…
Reference in a new issue