"""Builder commands for world editing.""" from pathlib import Path from mudlib.commands import CommandDefinition, register from mudlib.export import export_zone_to_file from mudlib.player import Player, players from mudlib.store import account_exists, load_player_data, set_admin from mudlib.things import spawn_thing, thing_templates from mudlib.zone import Zone from mudlib.zones import get_zone, register_zone, zone_registry # Content directory, set during server startup _content_dir: Path | None = None def set_content_dir(path: Path) -> None: """Set the content directory for saving zones.""" global _content_dir _content_dir = path async def cmd_goto(player: Player, args: str) -> None: """Teleport to a named zone's spawn point.""" zone_name = args.strip() if not zone_name: await player.send("Usage: @goto \r\n") return target = get_zone(zone_name) if target is None: await player.send(f"Zone '{zone_name}' not found.\r\n") return player.move_to(target, x=target.spawn_x, y=target.spawn_y) await player.send(f"Teleported to {zone_name}.\r\n") from mudlib.commands.look import cmd_look await cmd_look(player, "") async def cmd_dig(player: Player, args: str) -> None: """Create a new blank zone and teleport there.""" parts = args.strip().split() if len(parts) != 3: await player.send("Usage: @dig \r\n") return name = parts[0] try: width = int(parts[1]) height = int(parts[2]) except ValueError: await player.send("Width and height must be numbers.\r\n") return if width < 1 or height < 1: await player.send("Zone dimensions must be at least 1x1.\r\n") return if get_zone(name) is not None: await player.send(f"Zone '{name}' already exists.\r\n") return terrain = [["." for _ in range(width)] for _ in range(height)] zone = Zone( name=name, width=width, height=height, terrain=terrain, toroidal=False, ) register_zone(name, zone) player.move_to(zone, x=0, y=0) await player.send(f"Created zone '{name}' ({width}x{height}).\r\n") from mudlib.commands.look import cmd_look await cmd_look(player, "") async def cmd_save(player: Player, args: str) -> None: """Save the current zone to its TOML file.""" zone = player.location if not isinstance(zone, Zone): await player.send("You're not in a zone.\r\n") return if _content_dir is None: await player.send("Content directory not configured.\r\n") return zones_dir = _content_dir / "zones" zones_dir.mkdir(parents=True, exist_ok=True) path = zones_dir / f"{zone.name}.toml" export_zone_to_file(zone, path) await player.send(f"Zone '{zone.name}' saved to {path.name}.\r\n") async def cmd_place(player: Player, args: str) -> None: """Place a thing from templates at the player's position.""" thing_name = args.strip() if not thing_name: await player.send("Usage: @place \r\n") return template = thing_templates.get(thing_name) if template is None: await player.send(f"Thing template '{thing_name}' not found.\r\n") return spawn_thing(template, player.location, x=player.x, y=player.y) await player.send(f"Placed {thing_name} at ({player.x}, {player.y}).\r\n") async def cmd_promote(player: Player, args: str) -> None: """Grant admin status to a player.""" target_name = args.strip() if not target_name: await player.send("Usage: @promote \r\n") return if not account_exists(target_name): await player.send(f"Account '{target_name}' not found.\r\n") return # Check current status via live player or DB data = load_player_data(target_name) if data and data.get("is_admin"): await player.send(f"{target_name} is already an admin.\r\n") return set_admin(target_name, True) # Update live player if online target = next( (p for p in players.values() if p.name.lower() == target_name.lower()), None, ) if target is not None: target.is_admin = True await target.send("You have been granted admin status.\r\n") await player.send(f"{target_name} is now an admin.\r\n") async def cmd_demote(player: Player, args: str) -> None: """Revoke admin status from a player.""" target_name = args.strip() if not target_name: await player.send("Usage: @demote \r\n") return if target_name.lower() == player.name.lower(): await player.send("You can't demote yourself.\r\n") return if not account_exists(target_name): await player.send(f"Account '{target_name}' not found.\r\n") return data = load_player_data(target_name) if data and not data.get("is_admin"): await player.send(f"{target_name} is not an admin.\r\n") return set_admin(target_name, False) # Update live player if online target = next( (p for p in players.values() if p.name.lower() == target_name.lower()), None, ) if target is not None: target.is_admin = False await target.send("Your admin status has been revoked.\r\n") await player.send(f"{target_name} is no longer an admin.\r\n") async def cmd_zones(player: Player, args: str) -> None: """List all registered zones.""" if not zone_registry: await player.send("No zones registered.\r\n") return await player.send("zones:\r\n") current_zone_name = ( player.location.name if isinstance(player.location, Zone) else None ) for name in sorted(zone_registry.keys()): zone = zone_registry[name] dimensions = f"{zone.width}x{zone.height}" marker = " [here]" if name == current_zone_name else "" await player.send(f" {name:<15} {dimensions:>6}{marker}\r\n") register(CommandDefinition("@goto", cmd_goto, admin=True, help="Teleport to a zone")) register(CommandDefinition("@dig", cmd_dig, admin=True, help="Create a new zone")) register(CommandDefinition("@save", cmd_save, admin=True, help="Save current zone")) register(CommandDefinition("@place", cmd_place, admin=True, help="Place a thing")) register( CommandDefinition("@promote", cmd_promote, admin=True, help="Grant admin status") ) register( CommandDefinition("@demote", cmd_demote, admin=True, help="Revoke admin status") ) register(CommandDefinition("@zones", cmd_zones, admin=True, help="List all zones"))