Skip to content

Commit 0a667d2

Browse files
authored
Send game_info message if social add/remove changes game visibility (#1029)
1 parent ede2a07 commit 0a667d2

File tree

2 files changed

+269
-4
lines changed

2 files changed

+269
-4
lines changed

server/lobbyconnection.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,20 @@ async def command_social_remove(self, message):
333333
friends_and_foes.c.subject_id == subject_id
334334
)))
335335

336-
with contextlib.suppress(KeyError):
337-
player_attr.remove(subject_id)
336+
game = self.player.game
337+
visibility_context_manager = contextlib.nullcontext()
338+
339+
if game and game.host == self.player:
340+
# If the player is currently hosting a game, we need to make sure
341+
# that the visibility change is sent to the subject
342+
subject = self.player_service.get_player(subject_id)
343+
visibility_context_manager = self._write_visibility_change_context(
344+
game,
345+
subject,
346+
)
347+
348+
with visibility_context_manager:
349+
player_attr.discard(subject_id)
338350

339351
async def command_social_add(self, message):
340352
if "friend" in message:
@@ -358,7 +370,48 @@ async def command_social_add(self, message):
358370
subject_id=subject_id,
359371
))
360372

361-
player_attr.add(subject_id)
373+
game = self.player.game
374+
visibility_context_manager = contextlib.nullcontext()
375+
376+
if game and game.host == self.player:
377+
# If the player is currently hosting a game, we need to make sure
378+
# that the visibility change is sent to the subject
379+
subject = self.player_service.get_player(subject_id)
380+
visibility_context_manager = self._write_visibility_change_context(
381+
game,
382+
subject,
383+
)
384+
385+
with visibility_context_manager:
386+
player_attr.add(subject_id)
387+
388+
@contextlib.contextmanager
389+
def _write_visibility_change_context(
390+
self,
391+
game: Game,
392+
player: Player,
393+
):
394+
# Check visibility before/after
395+
was_visible = game.is_visible_to_player(player)
396+
yield
397+
is_visible = game.is_visible_to_player(player)
398+
399+
if was_visible is is_visible:
400+
return
401+
402+
self._logger.debug(
403+
"Visibility for %s changed for %s from %s -> %s",
404+
game,
405+
player.login,
406+
was_visible,
407+
is_visible,
408+
)
409+
410+
msg = game.to_dict()
411+
if not is_visible:
412+
msg["state"] = "closed"
413+
414+
player.write_message(msg)
362415

363416
async def kick(self):
364417
await self.send({

tests/integration_tests/test_server.py

Lines changed: 213 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sqlalchemy import and_, select
77

88
from server.config import config
9-
from server.db.models import avatars, avatars_list, ban
9+
from server.db.models import avatars, avatars_list, ban, friends_and_foes
1010
from server.protocol import DisconnectedError
1111
from tests.utils import fast_forward
1212

@@ -996,3 +996,215 @@ async def test_avatar_select_not_owned(lobby_server, database):
996996
async with database.acquire() as conn:
997997
result = await get_player_selected_avatars(conn, player_id)
998998
assert result.rowcount == 0
999+
1000+
1001+
@fast_forward(30)
1002+
async def test_social_add_friend(lobby_server, database):
1003+
subject_id = 10
1004+
player_id, _, proto = await connect_and_sign_in(
1005+
("test", "test_password"),
1006+
lobby_server,
1007+
)
1008+
await read_until_command(proto, "game_info")
1009+
1010+
async with database.acquire() as conn:
1011+
result = await conn.execute(
1012+
select(friends_and_foes)
1013+
.where(
1014+
and_(
1015+
friends_and_foes.c.user_id == player_id,
1016+
friends_and_foes.c.subject_id == subject_id,
1017+
)
1018+
)
1019+
)
1020+
row = result.fetchone()
1021+
assert row is None
1022+
1023+
# Other player doesn't even need to be online
1024+
await proto.send_message({
1025+
"command": "social_add",
1026+
"friend": subject_id,
1027+
})
1028+
await asyncio.sleep(5)
1029+
1030+
async with database.acquire() as conn:
1031+
result = await conn.execute(
1032+
select(friends_and_foes)
1033+
.where(
1034+
and_(
1035+
friends_and_foes.c.user_id == player_id,
1036+
friends_and_foes.c.subject_id == subject_id,
1037+
)
1038+
)
1039+
)
1040+
row = result.fetchone()
1041+
1042+
assert row.subject_id == subject_id
1043+
assert row.status == "FRIEND"
1044+
1045+
1046+
@fast_forward(30)
1047+
async def test_social_add_foe(lobby_server, database):
1048+
subject_id = 10
1049+
player_id, _, proto = await connect_and_sign_in(
1050+
("test", "test_password"),
1051+
lobby_server,
1052+
)
1053+
await read_until_command(proto, "game_info")
1054+
1055+
async with database.acquire() as conn:
1056+
result = await conn.execute(
1057+
select(friends_and_foes)
1058+
.where(
1059+
and_(
1060+
friends_and_foes.c.user_id == player_id,
1061+
friends_and_foes.c.subject_id == subject_id,
1062+
)
1063+
)
1064+
)
1065+
row = result.fetchone()
1066+
assert row is None
1067+
1068+
# Other player doesn't even need to be online
1069+
await proto.send_message({
1070+
"command": "social_add",
1071+
"foe": subject_id,
1072+
})
1073+
await asyncio.sleep(5)
1074+
1075+
async with database.acquire() as conn:
1076+
result = await conn.execute(
1077+
select(friends_and_foes)
1078+
.where(
1079+
and_(
1080+
friends_and_foes.c.user_id == player_id,
1081+
friends_and_foes.c.subject_id == subject_id,
1082+
)
1083+
)
1084+
)
1085+
row = result.fetchone()
1086+
1087+
assert row.subject_id == subject_id
1088+
assert row.status == "FOE"
1089+
1090+
1091+
@fast_forward(30)
1092+
async def test_social_add_friend_while_hosting(lobby_server):
1093+
_, _, proto1 = await connect_and_sign_in(
1094+
("test", "test_password"),
1095+
lobby_server,
1096+
)
1097+
rhiza_id, _, proto2 = await connect_and_sign_in(
1098+
("Rhiza", "puff_the_magic_dragon"),
1099+
lobby_server,
1100+
)
1101+
await read_until_command(proto1, "game_info")
1102+
await read_until_command(proto2, "game_info")
1103+
1104+
game_id = await host_game(proto1, visibility="friends")
1105+
with pytest.raises(asyncio.TimeoutError):
1106+
await read_until_command(proto2, "game_info", timeout=10, uid=game_id)
1107+
1108+
await proto1.send_message({
1109+
"command": "social_add",
1110+
"friend": rhiza_id,
1111+
})
1112+
1113+
await read_until_command(
1114+
proto2,
1115+
"game_info",
1116+
timeout=10,
1117+
uid=game_id,
1118+
state="open",
1119+
)
1120+
1121+
1122+
@fast_forward(30)
1123+
async def test_social_add_foe_while_hosting(lobby_server):
1124+
_, _, proto1 = await connect_and_sign_in(
1125+
("test", "test_password"),
1126+
lobby_server,
1127+
)
1128+
rhiza_id, _, proto2 = await connect_and_sign_in(
1129+
("Rhiza", "puff_the_magic_dragon"),
1130+
lobby_server,
1131+
)
1132+
await read_until_command(proto1, "game_info")
1133+
await read_until_command(proto2, "game_info")
1134+
1135+
game_id = await host_game(proto1)
1136+
await read_until_command(proto2, "game_info", timeout=10, uid=game_id)
1137+
1138+
await proto1.send_message({
1139+
"command": "social_add",
1140+
"foe": rhiza_id,
1141+
})
1142+
1143+
await read_until_command(
1144+
proto2,
1145+
"game_info",
1146+
timeout=10,
1147+
uid=game_id,
1148+
state="closed",
1149+
)
1150+
1151+
1152+
@fast_forward(30)
1153+
async def test_social_remove_friend_while_hosting(lobby_server):
1154+
_, _, proto1 = await connect_and_sign_in(
1155+
("friends", "friends"),
1156+
lobby_server,
1157+
)
1158+
test_id, _, proto2 = await connect_and_sign_in(
1159+
("test", "test_password"),
1160+
lobby_server,
1161+
)
1162+
await read_until_command(proto1, "game_info")
1163+
await read_until_command(proto2, "game_info")
1164+
1165+
game_id = await host_game(proto1, visibility="friends")
1166+
await read_until_command(proto2, "game_info", timeout=5, uid=game_id)
1167+
1168+
await proto1.send_message({
1169+
"command": "social_remove",
1170+
"friend": test_id,
1171+
})
1172+
1173+
await read_until_command(
1174+
proto2,
1175+
"game_info",
1176+
timeout=10,
1177+
uid=game_id,
1178+
state="closed",
1179+
)
1180+
1181+
1182+
@fast_forward(30)
1183+
async def test_social_remove_foe_while_hosting(lobby_server):
1184+
_, _, proto1 = await connect_and_sign_in(
1185+
("test", "test_password"),
1186+
lobby_server,
1187+
)
1188+
rhiza_id, _, proto2 = await connect_and_sign_in(
1189+
("foed_by_test", "foe"),
1190+
lobby_server,
1191+
)
1192+
await read_until_command(proto1, "game_info")
1193+
await read_until_command(proto2, "game_info")
1194+
1195+
game_id = await host_game(proto1)
1196+
with pytest.raises(asyncio.TimeoutError):
1197+
await read_until_command(proto2, "game_info", timeout=5, uid=game_id)
1198+
1199+
await proto1.send_message({
1200+
"command": "social_remove",
1201+
"foe": rhiza_id,
1202+
})
1203+
1204+
await read_until_command(
1205+
proto2,
1206+
"game_info",
1207+
timeout=10,
1208+
uid=game_id,
1209+
state="open",
1210+
)

0 commit comments

Comments
 (0)