"""Mob template loading, global registry, and spawn/despawn/query.""" import tomllib from dataclasses import dataclass, field from pathlib import Path from mudlib.entity import Mob from mudlib.zone import Zone @dataclass class MobTemplate: """Definition loaded from TOML — used to spawn Mob instances.""" name: str description: str pl: float stamina: float max_stamina: float moves: list[str] = field(default_factory=list) # Module-level registries mob_templates: dict[str, MobTemplate] = {} mobs: list[Mob] = [] def load_mob_template(path: Path) -> MobTemplate: """Parse a mob TOML file into a MobTemplate.""" with open(path, "rb") as f: data = tomllib.load(f) return MobTemplate( name=data["name"], description=data["description"], pl=data["pl"], stamina=data["stamina"], max_stamina=data["max_stamina"], moves=data.get("moves", []), ) def load_mob_templates(directory: Path) -> dict[str, MobTemplate]: """Load all .toml files in a directory into a dict keyed by name.""" templates: dict[str, MobTemplate] = {} for path in sorted(directory.glob("*.toml")): template = load_mob_template(path) templates[template.name] = template return templates def spawn_mob(template: MobTemplate, x: int, y: int, zone: Zone) -> Mob: """Create a Mob instance from a template at the given position. Args: template: The mob template to spawn from x: X coordinate in the zone y: Y coordinate in the zone zone: The zone where the mob will be spawned Returns: The spawned Mob instance """ mob = Mob( name=template.name, location=zone, x=x, y=y, pl=template.pl, stamina=template.stamina, max_stamina=template.max_stamina, description=template.description, moves=list(template.moves), ) mobs.append(mob) return mob def despawn_mob(mob: Mob) -> None: """Remove a mob from the registry and mark it dead.""" mob.alive = False if mob in mobs: mobs.remove(mob) def get_nearby_mob( name: str, x: int, y: int, zone: Zone, range_: int = 10 ) -> Mob | None: """Find the closest alive mob matching name within range. Uses zone.contents_near() to find all nearby objects, then filters for alive mobs matching the name and picks the closest. Args: name: Name of the mob to find x: X coordinate of the search center y: Y coordinate of the search center zone: The zone to search in range_: Maximum Manhattan distance (default 10) Returns: The closest matching mob, or None if none found """ best: Mob | None = None best_dist = float("inf") # Get all nearby objects from the zone nearby = zone.contents_near(x, y, range_) for obj in nearby: # Filter for alive mobs matching the name if not isinstance(obj, Mob) or not obj.alive or obj.name != name: continue # Calculate wrapping-aware distance to find closest dx = abs(obj.x - x) dy = abs(obj.y - y) if zone.toroidal: dx = min(dx, zone.width - dx) dy = min(dy, zone.height - dy) dist = dx + dy if dist < best_dist: best = obj best_dist = dist return best