Fix GMCP and MSDP support for rich clients
This commit is contained in:
parent
ca282851e3
commit
3d386fbf99
3 changed files with 24 additions and 11 deletions
|
|
@ -20,6 +20,7 @@ class Entity(Object):
|
||||||
y: int = 0
|
y: int = 0
|
||||||
# Combat stats
|
# Combat stats
|
||||||
pl: float = 100.0 # power level (health and damage multiplier)
|
pl: float = 100.0 # power level (health and damage multiplier)
|
||||||
|
max_pl: float = 100.0 # maximum power level
|
||||||
stamina: float = 100.0 # current stamina
|
stamina: float = 100.0 # current stamina
|
||||||
max_stamina: float = 100.0 # stamina ceiling
|
max_stamina: float = 100.0 # stamina ceiling
|
||||||
defense_locked_until: float = 0.0 # monotonic time when defense recovery ends
|
defense_locked_until: float = 0.0 # monotonic time when defense recovery ends
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def send_char_vitals(player: Player) -> None:
|
def send_char_vitals(player: Player) -> None:
|
||||||
"""Send Char.Vitals — pl, stamina, max_stamina."""
|
"""Send Char.Vitals — pl, max_pl, stamina, max_stamina."""
|
||||||
player.writer.send_gmcp(
|
player.send_gmcp(
|
||||||
"Char.Vitals",
|
"Char.Vitals",
|
||||||
{
|
{
|
||||||
"pl": round(player.pl, 1),
|
"pl": round(player.pl, 1),
|
||||||
|
"max_pl": round(player.max_pl, 1),
|
||||||
"stamina": round(player.stamina, 1),
|
"stamina": round(player.stamina, 1),
|
||||||
"max_stamina": round(player.max_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:
|
def send_char_status(player: Player) -> None:
|
||||||
"""Send Char.Status — flying, resting, mode, in_combat."""
|
"""Send Char.Status — flying, resting, mode, in_combat."""
|
||||||
player.writer.send_gmcp(
|
player.send_gmcp(
|
||||||
"Char.Status",
|
"Char.Status",
|
||||||
{
|
{
|
||||||
"flying": player.flying,
|
"flying": player.flying,
|
||||||
|
|
@ -53,13 +54,17 @@ def send_room_info(player: Player) -> None:
|
||||||
("south", 0, 1),
|
("south", 0, 1),
|
||||||
("east", 1, 0),
|
("east", 1, 0),
|
||||||
("west", -1, 0),
|
("west", -1, 0),
|
||||||
|
("northeast", 1, -1),
|
||||||
|
("northwest", -1, -1),
|
||||||
|
("southeast", 1, 1),
|
||||||
|
("southwest", -1, 1),
|
||||||
]
|
]
|
||||||
for name, dx, dy in directions:
|
for name, dx, dy in directions:
|
||||||
nx, ny = player.x + dx, player.y + dy
|
nx, ny = player.x + dx, player.y + dy
|
||||||
if zone.is_passable(nx, ny):
|
if zone.is_passable(nx, ny):
|
||||||
exits.append(name)
|
exits.append(name)
|
||||||
|
|
||||||
player.writer.send_gmcp(
|
player.send_gmcp(
|
||||||
"Room.Info",
|
"Room.Info",
|
||||||
{
|
{
|
||||||
"zone": zone.name,
|
"zone": zone.name,
|
||||||
|
|
@ -90,7 +95,7 @@ def send_map_data(player: Player) -> None:
|
||||||
row.append(zone.terrain[wy][wx])
|
row.append(zone.terrain[wy][wx])
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
|
||||||
player.writer.send_gmcp(
|
player.send_gmcp(
|
||||||
"Room.Map",
|
"Room.Map",
|
||||||
{
|
{
|
||||||
"x": player.x,
|
"x": player.x,
|
||||||
|
|
@ -103,10 +108,10 @@ def send_map_data(player: Player) -> None:
|
||||||
|
|
||||||
def send_msdp_vitals(player: Player) -> None:
|
def send_msdp_vitals(player: Player) -> None:
|
||||||
"""Send MSDP variable updates for real-time gauges."""
|
"""Send MSDP variable updates for real-time gauges."""
|
||||||
player.writer.send_msdp(
|
player.send_msdp(
|
||||||
{
|
{
|
||||||
"HEALTH": str(round(player.pl, 1)),
|
"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": str(round(player.stamina, 1)),
|
||||||
"STAMINA_MAX": str(round(player.max_stamina, 1)),
|
"STAMINA_MAX": str(round(player.max_stamina, 1)),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ def player(mock_writer, test_zone):
|
||||||
p = Player(name="Goku", x=10, y=10, writer=mock_writer)
|
p = Player(name="Goku", x=10, y=10, writer=mock_writer)
|
||||||
p.location = test_zone
|
p.location = test_zone
|
||||||
p.pl = 85.5
|
p.pl = 85.5
|
||||||
|
p.max_pl = 100.0
|
||||||
p.stamina = 42.3
|
p.stamina = 42.3
|
||||||
p.max_stamina = 100.0
|
p.max_stamina = 100.0
|
||||||
players[p.name] = p
|
players[p.name] = p
|
||||||
|
|
@ -69,6 +70,7 @@ def test_send_char_vitals(player):
|
||||||
"Char.Vitals",
|
"Char.Vitals",
|
||||||
{
|
{
|
||||||
"pl": 85.5,
|
"pl": 85.5,
|
||||||
|
"max_pl": 100.0,
|
||||||
"stamina": 42.3,
|
"stamina": 42.3,
|
||||||
"max_stamina": 100.0,
|
"max_stamina": 100.0,
|
||||||
},
|
},
|
||||||
|
|
@ -131,13 +133,15 @@ def test_send_room_info(player):
|
||||||
assert data["x"] == 10
|
assert data["x"] == 10
|
||||||
assert data["y"] == 10
|
assert data["y"] == 10
|
||||||
assert data["terrain"] == "."
|
assert data["terrain"] == "."
|
||||||
# All adjacent tiles should be passable except north (mountain at 10,5)
|
# All adjacent tiles should be passable (player at 10,10, all tiles are ".")
|
||||||
# north is y-1 = 9, but mountain is at y=5
|
|
||||||
# Player at 10,10 so adjacent tiles are all "." and passable
|
|
||||||
assert "north" in data["exits"]
|
assert "north" in data["exits"]
|
||||||
assert "south" in data["exits"]
|
assert "south" in data["exits"]
|
||||||
assert "east" in data["exits"]
|
assert "east" in data["exits"]
|
||||||
assert "west" 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):
|
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(
|
player.writer.send_msdp.assert_called_once_with(
|
||||||
{
|
{
|
||||||
"HEALTH": "85.5",
|
"HEALTH": "85.5",
|
||||||
"HEALTH_MAX": "85.5",
|
"HEALTH_MAX": "100.0",
|
||||||
"STAMINA": "42.3",
|
"STAMINA": "42.3",
|
||||||
"STAMINA_MAX": "100.0",
|
"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][0] == "Char.Vitals"
|
||||||
assert second_call[0][1] == {
|
assert second_call[0][1] == {
|
||||||
"pl": round(player.pl, 1),
|
"pl": round(player.pl, 1),
|
||||||
|
"max_pl": round(player.max_pl, 1),
|
||||||
"stamina": round(player.max_stamina, 1),
|
"stamina": round(player.max_stamina, 1),
|
||||||
"max_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]
|
attacker_call = mock_writer_1.send_gmcp.call_args[0]
|
||||||
assert attacker_call[0] == "Char.Vitals"
|
assert attacker_call[0] == "Char.Vitals"
|
||||||
assert "pl" in attacker_call[1]
|
assert "pl" in attacker_call[1]
|
||||||
|
assert "max_pl" in attacker_call[1]
|
||||||
assert "stamina" in attacker_call[1]
|
assert "stamina" in attacker_call[1]
|
||||||
assert "max_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]
|
defender_call = mock_writer_2.send_gmcp.call_args[0]
|
||||||
assert defender_call[0] == "Char.Vitals"
|
assert defender_call[0] == "Char.Vitals"
|
||||||
assert "pl" in defender_call[1]
|
assert "pl" in defender_call[1]
|
||||||
|
assert "max_pl" in defender_call[1]
|
||||||
assert "stamina" in defender_call[1]
|
assert "stamina" in defender_call[1]
|
||||||
assert "max_stamina" in defender_call[1]
|
assert "max_stamina" in defender_call[1]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue