mud/src/mudlib/gmcp.py
Jared Miller 200cc00129
Dedupe MSDP vitals to avoid spamming idle clients
Cache last-sent values on Player and skip send_msdp() when
nothing changed. Idle players no longer get a packet every second.
2026-02-12 16:31:17 -05:00

133 lines
3.3 KiB
Python

"""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)