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
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
]
|
]
|
||||||
|
|
|
||||||
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