"""GMCP and MSDP package support for rich MUD clients.""" from __future__ import annotations import logging from typing import TYPE_CHECKING if TYPE_CHECKING: from mudlib.player import Player log = logging.getLogger(__name__) def send_char_vitals(player: Player) -> None: """Send Char.Vitals — pl, max_pl, stamina, max_stamina.""" if not player.gmcp_enabled: return player.send_gmcp( "Char.Vitals", { "pl": round(player.pl, 1), "max_pl": round(player.max_pl, 1), "stamina": round(player.stamina, 1), "max_stamina": round(player.max_stamina, 1), }, ) def send_char_status(player: Player) -> None: """Send Char.Status — flying, resting, mode, in_combat.""" if not player.gmcp_enabled: return player.send_gmcp( "Char.Status", { "flying": player.flying, "resting": player.resting, "mode": player.mode, "in_combat": player.mode == "combat", }, ) def send_room_info(player: Player) -> None: """Send Room.Info — zone, coordinates, terrain, exits.""" if not player.gmcp_enabled: return from mudlib.zone import Zone zone = player.location if not isinstance(zone, Zone): return terrain = zone.get_tile(player.x, player.y) # Build exits list from passable adjacent tiles exits = [] directions = [ ("north", 0, -1), ("south", 0, 1), ("east", 1, 0), ("west", -1, 0), ("northeast", 1, -1), ("northwest", -1, -1), ("southeast", 1, 1), ("southwest", -1, 1), ] for name, dx, dy in directions: nx, ny = player.x + dx, player.y + dy if zone.is_passable(nx, ny): exits.append(name) player.send_gmcp( "Room.Info", { "zone": zone.name, "x": player.x, "y": player.y, "terrain": terrain, "exits": exits, }, ) def send_map_data(player: Player) -> None: """Send Room.Map — terrain viewport around player for client rendering.""" if not player.gmcp_enabled: return from mudlib.zone import Zone zone = player.location if not isinstance(zone, Zone): return radius = 10 rows = [] for dy in range(-radius, radius + 1): row = [] for dx in range(-radius, radius + 1): tx = player.x + dx ty = player.y + dy wx, wy = zone.wrap(tx, ty) row.append(zone.terrain[wy][wx]) rows.append(row) player.send_gmcp( "Room.Map", { "x": player.x, "y": player.y, "radius": radius, "terrain": rows, }, ) def send_msdp_vitals(player: Player) -> None: """Send MSDP variable updates for real-time gauges. Skips sending if values haven't changed since last send. """ if not player.msdp_enabled: return data = { "HEALTH": str(round(player.pl, 1)), "HEALTH_MAX": str(round(player.max_pl, 1)), "STAMINA": str(round(player.stamina, 1)), "STAMINA_MAX": str(round(player.max_stamina, 1)), } if data == player._last_msdp: return player._last_msdp = data player.send_msdp(data)