Skip to content

Commit eb2b327

Browse files
committed
add all friends to announcements
use new friendtracker (Wesmania) to signal friend-game events add player-info (and runtime-info on client start) stash signals on client start until chatter is added
1 parent b90bffa commit eb2b327

File tree

4 files changed

+227
-58
lines changed

4 files changed

+227
-58
lines changed

src/chat/channel.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,8 @@ def resize_map_column(self):
413413

414414
def add_chatter(self, chatter, join=False):
415415
"""
416-
Adds an user to this chat channel, and assigns an appropriate icon depending on friendship and FAF player status
416+
Adds an user to this chat channel, and assigns an appropriate icon
417+
depending on friendship and FAF player status
417418
"""
418419
if chatter not in self.chatters:
419420
item = Chatter(self.nickList, chatter, self,
@@ -427,6 +428,9 @@ def add_chatter(self, chatter, join=False):
427428
if join and self.chat_widget.client.joinsparts:
428429
self.print_action(chatter.name, "joined the channel.", server_action=True)
429430

431+
if chatter.player is not None and chatter.player.currentGame is not None:
432+
self.chat_widget.client.game_announcer.delayed_friend_events(chatter.player)
433+
430434
def remove_chatter(self, chatter, server_action=None):
431435
if chatter in self.chatters:
432436
self.nickList.removeRow(self.chatters[chatter].row())

src/chat/friendtracker.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from PyQt5.QtCore import QObject, pyqtSignal
2+
from enum import Enum
3+
from model.game import GameState
4+
5+
class FriendEvents(Enum):
6+
HOSTING_GAME = 1
7+
JOINED_GAME = 2
8+
REPLAY_AVAILABLE = 3
9+
10+
11+
class OnlineFriendsTracker(QObject):
12+
"""
13+
Keeps track of current online friends. Notifies about added or removed
14+
friends, no matter if it happens through (dis)connecting or through
15+
the user adding or removing friends.
16+
"""
17+
friendAdded = pyqtSignal(object)
18+
friendRemoved = pyqtSignal(object)
19+
20+
def __init__(self, me, playerset):
21+
QObject.__init__(self)
22+
self.friends = set()
23+
self._me = me
24+
self._playerset = playerset
25+
26+
self._me.relationsUpdated.connect(self._update_friends)
27+
self._playerset.playerAdded.connect(self._add_or_update_player)
28+
self._playerset.playerRemoved.connect(self._remove_player)
29+
30+
for player in self._playerset:
31+
self._add_or_update_player(player)
32+
33+
def _is_friend(self, player):
34+
return self._me.isFriend(player.id)
35+
36+
def _add_friend(self, player):
37+
if player in self.friends:
38+
return
39+
self.friends.add(player)
40+
self.friendAdded.emit(player)
41+
42+
def _remove_friend(self, player):
43+
if player not in self.friends:
44+
return
45+
self.friends.remove(player)
46+
self.friendRemoved.emit(player)
47+
48+
def _add_or_update_player(self, player):
49+
if self._is_friend(player):
50+
self._add_friend(player)
51+
else:
52+
self._remove_friend(player)
53+
54+
def _remove_player(self, player):
55+
self._remove_friend(player)
56+
57+
def _update_friends(self, player_ids):
58+
for pid in player_ids:
59+
try:
60+
player = self._playerset[pid]
61+
except KeyError:
62+
continue
63+
self._add_or_update_player(player)
64+
65+
66+
class FriendEventTracker(QObject):
67+
"""
68+
Tracks and notifies about interesting events of a single friend player.
69+
"""
70+
friendEvent = pyqtSignal(object, object)
71+
72+
def __init__(self, friend):
73+
QObject.__init__(self)
74+
self._friend = friend
75+
self._friend_game = None
76+
friend.newCurrentGame.connect(self._on_new_friend_game)
77+
self._reconnect_game_signals()
78+
79+
def _on_new_friend_game(self):
80+
self._reconnect_game_signals()
81+
self._check_game_joining_event()
82+
83+
def _reconnect_game_signals(self):
84+
old_game = self._friend_game
85+
if old_game is not None:
86+
old_game.liveReplayAvailable.disconnect(
87+
self._check_game_replay_event)
88+
89+
new_game = self._friend.currentGame
90+
self._friend_game = new_game
91+
if new_game is not None:
92+
new_game.liveReplayAvailable.connect(
93+
self._check_game_replay_event)
94+
95+
def _check_game_joining_event(self):
96+
if self._friend_game is None:
97+
return
98+
if self._friend_game.state == GameState.OPEN:
99+
if self._friend_game.host == self._friend.login:
100+
self.friendEvent.emit(self._friend, FriendEvents.HOSTING_GAME)
101+
else:
102+
self.friendEvent.emit(self._friend, FriendEvents.JOINED_GAME)
103+
104+
def _check_game_replay_event(self):
105+
if self._friend_game is None:
106+
return
107+
if not self._friend_game.has_live_replay:
108+
return
109+
self.friendEvent.emit(self._friend, FriendEvents.REPLAY_AVAILABLE)
110+
111+
def report_all_events(self):
112+
self._check_game_joining_event()
113+
self._check_game_replay_event()
114+
115+
116+
class FriendsEventTracker(QObject):
117+
"""
118+
Forwards notifications about all online friend players.
119+
FIXME: we duplicate all friend tracker signals here, is there a more
120+
elegant way? Maybe an enum and a single signal?
121+
"""
122+
friendEvent = pyqtSignal(object, object)
123+
124+
def __init__(self, online_friend_tracker):
125+
QObject.__init__(self)
126+
self._online_friend_tracker = online_friend_tracker
127+
self._friend_event_trackers = {}
128+
129+
self._online_friend_tracker.friendAdded.connect(self._add_friend)
130+
self._online_friend_tracker.friendRemoved.connect(self._remove_friend)
131+
132+
for friend in self._online_friend_tracker.friends:
133+
self._add_friend(friend)
134+
135+
def _add_friend(self, friend):
136+
tracker = FriendEventTracker(friend)
137+
tracker.friendEvent.connect(self.friendEvent.emit)
138+
self._friend_event_trackers[friend.id] = tracker
139+
140+
# No risk of reporting an event twice - either it didn't happen yet
141+
# so it won't be reported here, or it happened already so it wasn't
142+
# tracked
143+
tracker.report_all_events()
144+
145+
def _remove_friend(self, friend):
146+
try:
147+
# Signals get disconnected automatically since tracker is
148+
# no longer referenced.
149+
del self._friend_event_trackers[friend.id]
150+
except KeyError:
151+
pass
152+
153+
154+
def build_friends_tracker(me, playerset):
155+
online_friend_tracker = OnlineFriendsTracker(me, playerset)
156+
friends_event_tracker = FriendsEventTracker(online_friend_tracker)
157+
return friends_event_tracker

src/client/_clientwindow.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,6 @@ def __init__(self, *args, **kwargs):
272272

273273
self.player_colors = PlayerColors(self.me)
274274

275-
self.game_announcer = GameAnnouncer(self.gameset, self.me,
276-
self.player_colors, self)
277-
278275
self.power = 0 # current user power
279276
self.id = 0
280277
# Initialize the Menu Bar according to settings etc.
@@ -296,22 +293,8 @@ def __init__(self, *args, **kwargs):
296293
self.modMenu = None
297294

298295
self._alias_window = AliasSearchWindow(self)
299-
#self.nFrame = NewsFrame()
300-
#self.whatsNewLayout.addWidget(self.nFrame)
301-
#self.nFrame.collapse()
302-
303-
#self.nFrame = NewsFrame()
304-
#self.whatsNewLayout.addWidget(self.nFrame)
305-
306-
#self.nFrame = NewsFrame()
307-
#self.whatsNewLayout.addWidget(self.nFrame)
308-
309-
310-
#self.WPApi = WPAPI(self)
311-
#self.WPApi.newsDone.connect(self.on_wpapi_done)
312-
#self.WPApi.download()
313296

314-
#self.controlsContainerLayout.setAlignment(self.pageControlFrame, QtCore.Qt.AlignRight)
297+
self.game_announcer = GameAnnouncer(self.players, self.me, self.player_colors, self)
315298

316299
@property
317300
def state(self):

src/client/gameannouncer.py

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,86 @@
1-
from PyQt5.QtCore import QTimer
2-
from model.game import GameState
3-
4-
from fa import maps
1+
from chat.friendtracker import build_friends_tracker, FriendEvents
2+
import time
53

64

75
class GameAnnouncer:
8-
ANNOUNCE_DELAY_SECS = 35
96

10-
def __init__(self, gameset, me, colors, client):
11-
self._gameset = gameset
7+
def __init__(self, playerset, me, colors, client):
128
self._me = me
139
self._colors = colors
1410
self._client = client
1511

16-
self._gameset.newLobby.connect(self._announce_hosting)
17-
self._gameset.newLiveReplay.connect(self._announce_replay)
12+
self._friends_event_tracker = build_friends_tracker(me, playerset)
13+
self._friends_event_tracker.friendEvent.connect(self._friend_event)
1814

1915
self.announce_games = True
2016
self.announce_replays = True
21-
self._delayed_host_list = []
17+
self._delayed_event_list = []
18+
self.delay_friend_events = True
2219

23-
def _is_friend_host(self, game):
24-
return (game.host_player is not None
25-
and self._me.isFriend(game.host_player.id))
20+
def _friend_event(self, player, event):
21+
if self.delay_friend_events:
22+
self._delayed_event_list.append((player, event))
23+
else:
24+
self._friend_announce(player, event)
2625

27-
def _announce_hosting(self, game):
28-
if not self._is_friend_host(game) or not self.announce_games:
26+
def delayed_friend_events(self, player):
27+
if not self.delay_friend_events:
2928
return
30-
announce_delay = QTimer()
31-
announce_delay.setSingleShot(True)
32-
announce_delay.setInterval(self.ANNOUNCE_DELAY_SECS * 1000)
33-
announce_delay.timeout.connect(self._delayed_announce_hosting)
34-
announce_delay.start()
35-
self._delayed_host_list.append((announce_delay, game))
36-
37-
def _delayed_announce_hosting(self):
38-
timer, game = self._delayed_host_list.pop(0)
39-
40-
if (not self._is_friend_host(game) or
41-
not self.announce_games or
42-
game.state != GameState.OPEN):
29+
if len(self._delayed_event_list) == 0:
30+
self.delay_friend_events = False
4331
return
44-
self._announce(game, "hosting")
32+
i = 0
33+
for event in self._delayed_event_list:
34+
if player in event:
35+
player, event = self._delayed_event_list.pop(i)
36+
self._friend_announce(player, event)
37+
i += 1
4538

46-
def _announce_replay(self, game):
47-
if not self._is_friend_host(game) or not self.announce_replays:
39+
def _friend_announce(self, player, event):
40+
if player.currentGame is None:
41+
return
42+
game = player.currentGame
43+
if event == FriendEvents.HOSTING_GAME:
44+
if not self.announce_games: # Menu Option Chat
45+
return
46+
if game.featured_mod == "ladder1v1":
47+
activity = "started"
48+
else:
49+
activity = "is <font color='GoldenRod'>hosting</font>"
50+
elif event == FriendEvents.JOINED_GAME:
51+
if not self.announce_games: # Menu Option Chat
52+
return
53+
if game.featured_mod == "ladder1v1":
54+
activity = "started"
55+
else:
56+
activity = "joined"
57+
elif event == FriendEvents.REPLAY_AVAILABLE:
58+
if not self.announce_replays: # Menu Option Chat
59+
return
60+
activity = "is playing live"
61+
else: # that shouldn't happen
4862
return
49-
self._announce(game, "playing live")
5063

51-
def _announce(self, game, activity):
52-
url = game.url(game.host_player.id).toString()
53-
url_color = self._colors.getColor("url")
54-
mapname = maps.getDisplayName(game.mapname)
55-
fmt = 'is {} {}<a style="color:{}" href="{}">{}</a> (on {})'
5664
if game.featured_mod == "faf":
5765
modname = ""
5866
else:
5967
modname = game.featured_mod + " "
60-
msg = fmt.format(activity, modname, url_color, url, game.title, mapname)
61-
self._client.forwardLocalBroadcast(game.host, msg)
68+
if game.featured_mod != "ladder1v1":
69+
player_info = " [{}/{}]".format(game.num_players, game.max_players)
70+
else:
71+
player_info = ""
72+
time_info = ""
73+
if game.has_live_replay:
74+
time_running = time.time() - game.launched_at
75+
if time_running > 6 * 60: # already running games on client start
76+
time_format = '%M:%S' if time_running < 60 * 60 else '%H:%M:%S'
77+
time_info = " runs {}"\
78+
.format(time.strftime(time_format, time.gmtime(time_running)))
79+
url_color = self._colors.getColor("url")
80+
url = game.url(player.id).toString()
81+
82+
fmt = '{} {}<a style="color:{}" href="{}">{}</a> ' \
83+
'(on <font color="GoldenRod">{}</font> {}{})'
84+
msg = fmt.format(activity, modname, url_color, url, game.title,
85+
game.mapdisplayname, player_info, time_info)
86+
self._client.forwardLocalBroadcast(player.login, msg)

0 commit comments

Comments
 (0)