Skip to content

Commit f31535d

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 client has settled to hopefully avoid the need for any timers ...
1 parent 2157502 commit f31535d

File tree

3 files changed

+219
-60
lines changed

3 files changed

+219
-60
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 & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,6 @@ def __init__(self, *args, **kwargs):
269269

270270
self.player_colors = PlayerColors(self.me)
271271

272-
self.game_announcer = GameAnnouncer(self.gameset, self.me,
273-
self.player_colors, self)
274-
275272
self.power = 0 # current user power
276273
self.id = 0
277274
# Initialize the Menu Bar according to settings etc.
@@ -293,22 +290,8 @@ def __init__(self, *args, **kwargs):
293290
self.modMenu = None
294291

295292
self._alias_window = AliasSearchWindow(self)
296-
#self.nFrame = NewsFrame()
297-
#self.whatsNewLayout.addWidget(self.nFrame)
298-
#self.nFrame.collapse()
299-
300-
#self.nFrame = NewsFrame()
301-
#self.whatsNewLayout.addWidget(self.nFrame)
302-
303-
#self.nFrame = NewsFrame()
304-
#self.whatsNewLayout.addWidget(self.nFrame)
305-
306-
307-
#self.WPApi = WPAPI(self)
308-
#self.WPApi.newsDone.connect(self.on_wpapi_done)
309-
#self.WPApi.download()
310293

311-
#self.controlsContainerLayout.setAlignment(self.pageControlFrame, QtCore.Qt.AlignRight)
294+
self.game_announcer = GameAnnouncer(self.players, self.me, self.player_colors, self)
312295

313296
@property
314297
def state(self):
@@ -1041,6 +1024,9 @@ def _tabChanged(self, tab, curr, prev):
10411024
def mainTabChanged(self, curr):
10421025
self._tabChanged(self.mainTabs, curr, self._main_tab)
10431026
self._main_tab = curr
1027+
# the tab change works after client has kinda initialized
1028+
if self.game_announcer.delay_friend_events:
1029+
self.game_announcer.delayed_friend_events()
10441030

10451031
@QtCore.pyqtSlot(int)
10461032
def vaultTabChanged(self, curr):

src/client/gameannouncer.py

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,77 @@
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:
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))
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):
43-
return
44-
self._announce(game, "hosting")
26+
def delayed_friend_events(self):
27+
self.delay_friend_events = False
28+
while len(self._delayed_event_list) > 0:
29+
player, event = self._delayed_event_list.pop(0)
30+
self._friend_announce(player, event)
4531

46-
def _announce_replay(self, game):
47-
if not self._is_friend_host(game) or not self.announce_replays:
32+
def _friend_announce(self, player, event):
33+
if player.currentGame is None:
4834
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 {})'
35+
game = player.currentGame
36+
if event == FriendEvents.HOSTING_GAME:
37+
if not self.announce_games: # Menu Option Chat
38+
return
39+
if game.featured_mod == "ladder1v1":
40+
activity = "started"
41+
else:
42+
activity = "is <font color='GoldenRod'>hosting</font>"
43+
elif event == FriendEvents.JOINED_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 = "joined"
50+
elif event == FriendEvents.REPLAY_AVAILABLE:
51+
if not self.announce_replays: # Menu Option Chat
52+
return
53+
activity = "is playing live"
54+
else: # that shouldn't happen
55+
activity = "<font color='Red'>has left the building</font>"
5656
if game.featured_mod == "faf":
5757
modname = ""
5858
else:
5959
modname = game.featured_mod + " "
60-
msg = fmt.format(activity, modname, url_color, url, game.title, mapname)
61-
self._client.forwardLocalBroadcast(game.host, msg)
60+
if game.featured_mod != "ladder1v1":
61+
player_info = " [{}/{}]".format(game.num_players, game.max_players)
62+
else:
63+
player_info = ""
64+
time_info = ""
65+
if game.has_live_replay:
66+
time_running = time.time() - game.launched_at
67+
if time_running > 6 * 60: # for already running games on client start
68+
time_format = '%M:%S' if time_running < 60 * 60 else '%H:%M:%S'
69+
time_info = " runs {}"\
70+
.format(time.strftime(time_format, time.gmtime(time_running)))
71+
url_color = self._colors.getColor("url")
72+
url = game.url(player.id).toString()
73+
fmt = '{} {}<a style="color:{}" href="{}">{}</a> ' \
74+
'(on <font color="GoldenRod">{}</font> {}{})'
75+
msg = fmt.format(activity, modname, url_color, url, game.title,
76+
game.mapdisplayname, player_info, time_info)
77+
self._client.forwardLocalBroadcast(player.login, msg)

0 commit comments

Comments
 (0)