Fix GMCP and MSDP support for rich clients

This commit is contained in:
Jared Miller 2026-02-11 23:07:09 -05:00
parent ca282851e3
commit 3d386fbf99
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
3 changed files with 24 additions and 11 deletions

View file

@ -20,6 +20,7 @@ class Entity(Object):
y: int = 0
# Combat stats
pl: float = 100.0 # power level (health and damage multiplier)
max_pl: float = 100.0 # maximum power level
stamina: float = 100.0 # current stamina
max_stamina: float = 100.0 # stamina ceiling
defense_locked_until: float = 0.0 # monotonic time when defense recovery ends

View file

@ -12,11 +12,12 @@ log = logging.getLogger(__name__)
def send_char_vitals(player: Player) -> None:
"""Send Char.Vitals — pl, stamina, max_stamina."""
player.writer.send_gmcp(
"""Send Char.Vitals — pl, max_pl, stamina, max_stamina."""
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),
},
@ -25,7 +26,7 @@ def send_char_vitals(player: Player) -> None:
def send_char_status(player: Player) -> None:
"""Send Char.Status — flying, resting, mode, in_combat."""
player.writer.send_gmcp(
player.send_gmcp(
"Char.Status",
{
"flying": player.flying,
@ -53,13 +54,17 @@ def send_room_info(player: Player) -> None:
("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.writer.send_gmcp(
player.send_gmcp(
"Room.Info",
{
"zone": zone.name,
@ -90,7 +95,7 @@ def send_map_data(player: Player) -> None:
row.append(zone.terrain[wy][wx])
rows.append(row)
player.writer.send_gmcp(
player.send_gmcp(
"Room.Map",
{
"x": player.x,
@ -103,10 +108,10 @@ def send_map_data(player: Player) -> None:
def send_msdp_vitals(player: Player) -> None:
"""Send MSDP variable updates for real-time gauges."""
player.writer.send_msdp(
player.send_msdp(
{
"HEALTH": str(round(player.pl, 1)),
"HEALTH_MAX": 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)),
}

View file

@ -55,6 +55,7 @@ def player(mock_writer, test_zone):
p = Player(name="Goku", x=10, y=10, writer=mock_writer)
p.location = test_zone
p.pl = 85.5
p.max_pl = 100.0
p.stamina = 42.3
p.max_stamina = 100.0
players[p.name] = p
@ -69,6 +70,7 @@ def test_send_char_vitals(player):
"Char.Vitals",
{
"pl": 85.5,
"max_pl": 100.0,
"stamina": 42.3,
"max_stamina": 100.0,
},
@ -131,13 +133,15 @@ def test_send_room_info(player):
assert data["x"] == 10
assert data["y"] == 10
assert data["terrain"] == "."
# All adjacent tiles should be passable except north (mountain at 10,5)
# north is y-1 = 9, but mountain is at y=5
# Player at 10,10 so adjacent tiles are all "." and passable
# All adjacent tiles should be passable (player at 10,10, all tiles are ".")
assert "north" in data["exits"]
assert "south" in data["exits"]
assert "east" in data["exits"]
assert "west" in data["exits"]
assert "northeast" in data["exits"]
assert "northwest" in data["exits"]
assert "southeast" in data["exits"]
assert "southwest" in data["exits"]
def test_send_room_info_blocked_exit(player, test_zone):
@ -215,7 +219,7 @@ def test_send_msdp_vitals(player):
player.writer.send_msdp.assert_called_once_with(
{
"HEALTH": "85.5",
"HEALTH_MAX": "85.5",
"HEALTH_MAX": "100.0",
"STAMINA": "42.3",
"STAMINA_MAX": "100.0",
}
@ -296,6 +300,7 @@ async def test_char_vitals_sent_on_rest_complete(player):
assert second_call[0][0] == "Char.Vitals"
assert second_call[0][1] == {
"pl": round(player.pl, 1),
"max_pl": round(player.max_pl, 1),
"stamina": round(player.max_stamina, 1),
"max_stamina": round(player.max_stamina, 1),
}
@ -364,6 +369,7 @@ async def test_char_vitals_sent_on_combat_resolve():
attacker_call = mock_writer_1.send_gmcp.call_args[0]
assert attacker_call[0] == "Char.Vitals"
assert "pl" in attacker_call[1]
assert "max_pl" in attacker_call[1]
assert "stamina" in attacker_call[1]
assert "max_stamina" in attacker_call[1]
@ -371,6 +377,7 @@ async def test_char_vitals_sent_on_combat_resolve():
defender_call = mock_writer_2.send_gmcp.call_args[0]
assert defender_call[0] == "Char.Vitals"
assert "pl" in defender_call[1]
assert "max_pl" in defender_call[1]
assert "stamina" in defender_call[1]
assert "max_stamina" in defender_call[1]