Add mob home regions to spawn rules and entity

This commit is contained in:
Jared Miller 2026-02-14 11:51:39 -05:00
parent 755d23aa13
commit 5e0ec120c6
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
5 changed files with 234 additions and 1 deletions

View file

@ -76,3 +76,7 @@ class Mob(Entity):
alive: bool = True alive: bool = True
moves: list[str] = field(default_factory=list) moves: list[str] = field(default_factory=list)
next_action_at: float = 0.0 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

View file

@ -64,7 +64,9 @@ def load_mob_templates(directory: Path) -> dict[str, MobTemplate]:
return templates 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. """Create a Mob instance from a template at the given position.
Args: Args:
@ -72,6 +74,7 @@ def spawn_mob(template: MobTemplate, x: int, y: int, zone: Zone) -> Mob:
x: X coordinate in the zone x: X coordinate in the zone
y: Y coordinate in the zone y: Y coordinate in the zone
zone: The zone where the mob will be spawned zone: The zone where the mob will be spawned
home_region: Optional home region dict with x and y bounds
Returns: Returns:
The spawned Mob instance The spawned Mob instance
@ -87,6 +90,11 @@ def spawn_mob(template: MobTemplate, x: int, y: int, zone: Zone) -> Mob:
description=template.description, description=template.description,
moves=list(template.moves), 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) mobs.append(mob)
return mob return mob

View file

@ -19,6 +19,7 @@ class SpawnRule:
mob: str mob: str
max_count: int = 1 max_count: int = 1
respawn_seconds: int = 300 respawn_seconds: int = 300
home_region: dict | None = None
@dataclass(eq=False) @dataclass(eq=False)

View file

@ -81,6 +81,7 @@ def load_zone(path: Path) -> Zone:
mob=spawn["mob"], mob=spawn["mob"],
max_count=spawn.get("max_count", 1), max_count=spawn.get("max_count", 1),
respawn_seconds=spawn.get("respawn_seconds", 300), respawn_seconds=spawn.get("respawn_seconds", 300),
home_region=spawn.get("home_region"),
) )
for spawn in spawns_data for spawn in spawns_data
] ]

View 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()