Skip to content

Commit 5901c86

Browse files
committed
add all friends to announcements
use new friendtracker (Wesmania) to signal friend-game events stash signals on client start until client has settled to avoid the need for any timers
1 parent b1b600a commit 5901c86

File tree

3 files changed

+202
-61
lines changed

3 files changed

+202
-61
lines changed

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: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ def __init__(self, *args, **kwargs):
169169
self.players = Playerset() # Players known to the client
170170

171171
self.gameset = Gameset(self.players)
172-
fa.instance.gameset = self.gameset # FIXME
173172

174173
# Handy reference to the User object representing the logged-in user.
175174
self.me = User(self.players)
@@ -286,9 +285,6 @@ def __init__(self, *args, **kwargs):
286285

287286
self.player_colors = PlayerColors(self.me)
288287

289-
self.game_announcer = GameAnnouncer(self.gameset, self.me,
290-
self.player_colors, self)
291-
292288
self.power = 0 # current user power
293289
self.id = 0
294290
# Initialize the Menu Bar according to settings etc.
@@ -310,22 +306,8 @@ def __init__(self, *args, **kwargs):
310306
self.modMenu = None
311307

312308
self._alias_window = AliasSearchWindow(self)
313-
#self.nFrame = NewsFrame()
314-
#self.whatsNewLayout.addWidget(self.nFrame)
315-
#self.nFrame.collapse()
316-
317-
#self.nFrame = NewsFrame()
318-
#self.whatsNewLayout.addWidget(self.nFrame)
319-
320-
#self.nFrame = NewsFrame()
321-
#self.whatsNewLayout.addWidget(self.nFrame)
322-
323-
324-
#self.WPApi = WPAPI(self)
325-
#self.WPApi.newsDone.connect(self.on_wpapi_done)
326-
#self.WPApi.download()
327309

328-
#self.controlsContainerLayout.setAlignment(self.pageControlFrame, QtCore.Qt.AlignRight)
310+
self.game_announcer = GameAnnouncer(self.players, self.me, self.player_colors, self)
329311

330312
@property
331313
def state(self):
@@ -1060,6 +1042,9 @@ def _tabChanged(self, tab, curr, prev):
10601042
def mainTabChanged(self, curr):
10611043
self._tabChanged(self.mainTabs, curr, self._main_tab)
10621044
self._main_tab = curr
1045+
# the tab change works after client has kinda initialized
1046+
if self.game_announcer.delay_friend_events:
1047+
self.game_announcer.delayed_friend_events()
10631048

10641049
@QtCore.pyqtSlot(int)
10651050
def vaultTabChanged(self, curr):

src/client/gameannouncer.py

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,60 @@
1-
from PyQt5.QtCore import QTimer
21
from model.game import GameState
3-
4-
from fa import maps
5-
2+
from chat.friendtracker import build_friends_tracker
63

74
class GameAnnouncer:
8-
ANNOUNCE_DELAY_SECS = 35
95

10-
def __init__(self, gameset, me, colors, client):
11-
self._gameset = gameset
6+
def __init__(self, playerset, me, colors, client):
127
self._me = me
138
self._colors = colors
149
self._client = client
1510

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

1914
self.announce_games = True
2015
self.announce_replays = True
21-
self._delayed_host_list = []
22-
23-
def _is_friend_host(self, game):
24-
return (game.host_player is not None
25-
and self._me.isFriend(game.host_player.id))
26-
27-
def _announce_hosting(self, game):
28-
if not self._is_friend_host(game) or not self.announce_games:
29-
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))
16+
self._delayed_event_list = []
17+
self.delay_friend_events = True
3618

37-
def _delayed_announce_hosting(self):
38-
timer, game = self._delayed_host_list.pop(0)
19+
def _friend_event(self, player, event):
20+
if self.delay_friend_events:
21+
self._delayed_event_list.append((player, event))
22+
else:
23+
self._friend_announce(player, event)
3924

40-
if (not self._is_friend_host(game) or
41-
not self.announce_games or
42-
game.state != GameState.OPEN):
43-
return
44-
self._announce(game, "hosting")
25+
def delayed_friend_events(self):
26+
self.delay_friend_events = False
27+
while len(self._delayed_event_list) > 0:
28+
player, event = self._delayed_event_list.pop(0)
29+
self._friend_announce(player, event)
4530

46-
def _announce_replay(self, game):
47-
if not self._is_friend_host(game) or not self.announce_replays:
31+
def _friend_announce(self, player, event):
32+
if player.currentGame is None:
4833
return
49-
self._announce(game, "playing live")
50-
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 {})'
34+
game = player.currentGame
35+
player_info = ""
36+
if game.state == GameState.OPEN:
37+
if not self.announce_games: # Menu Option Chat
38+
return
39+
if game.featured_mod == "ladder1v1":
40+
activity = "started"
41+
elif player.login == game.host:
42+
activity = "is <font color='GoldenRod'>hosting</font>"
43+
else:
44+
activity = "joined"
45+
player_info = " [{}/{}]".format(game.num_players, game.max_players)
46+
elif game.state == GameState.PLAYING:
47+
if not self.announce_replays: # Menu Option Chat
48+
return
49+
activity = "is playing live"
50+
else: # GameSate.CLOSED
51+
activity = "<font color='Red'>has left the building</font>"
5652
if game.featured_mod == "faf":
5753
modname = ""
5854
else:
5955
modname = game.featured_mod + " "
60-
msg = fmt.format(activity, modname, url_color, url, game.title, mapname)
61-
self._client.forwardLocalBroadcast(game.host, msg)
56+
url_color = self._colors.getColor("url")
57+
url = game.url(player.id).toString()
58+
fmt = '{} {}<a style="color:{}" href="{}">{}</a> (on {} {})'
59+
msg = fmt.format(activity, modname, url_color, url, game.title, game.mapdisplayname, player_info)
60+
self._client.forwardLocalBroadcast(player.login, msg)

0 commit comments

Comments
 (0)