-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
3 changed files
with
202 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
from PyQt5.QtCore import QObject, pyqtSignal | ||
from enum import Enum | ||
from model.game import GameState | ||
|
||
class FriendEvents(Enum): | ||
HOSTING_GAME = 1 | ||
JOINED_GAME = 2 | ||
REPLAY_AVAILABLE = 3 | ||
|
||
|
||
class OnlineFriendsTracker(QObject): | ||
""" | ||
Keeps track of current online friends. Notifies about added or removed | ||
friends, no matter if it happens through (dis)connecting or through | ||
the user adding or removing friends. | ||
""" | ||
friendAdded = pyqtSignal(object) | ||
friendRemoved = pyqtSignal(object) | ||
|
||
def __init__(self, me, playerset): | ||
QObject.__init__(self) | ||
self.friends = set() | ||
self._me = me | ||
self._playerset = playerset | ||
|
||
self._me.relationsUpdated.connect(self._update_friends) | ||
self._playerset.playerAdded.connect(self._add_or_update_player) | ||
self._playerset.playerRemoved.connect(self._remove_player) | ||
|
||
for player in self._playerset: | ||
self._add_or_update_player(player) | ||
|
||
def _is_friend(self, player): | ||
return self._me.isFriend(player.id) | ||
|
||
def _add_friend(self, player): | ||
if player in self.friends: | ||
return | ||
self.friends.add(player) | ||
self.friendAdded.emit(player) | ||
|
||
def _remove_friend(self, player): | ||
if player not in self.friends: | ||
return | ||
self.friends.remove(player) | ||
self.friendRemoved.emit(player) | ||
|
||
def _add_or_update_player(self, player): | ||
if self._is_friend(player): | ||
self._add_friend(player) | ||
else: | ||
self._remove_friend(player) | ||
|
||
def _remove_player(self, player): | ||
self._remove_friend(player) | ||
|
||
def _update_friends(self, player_ids): | ||
for pid in player_ids: | ||
try: | ||
player = self._playerset[pid] | ||
except KeyError: | ||
continue | ||
self._add_or_update_player(player) | ||
|
||
|
||
class FriendEventTracker(QObject): | ||
""" | ||
Tracks and notifies about interesting events of a single friend player. | ||
""" | ||
friendEvent = pyqtSignal(object, object) | ||
|
||
def __init__(self, friend): | ||
QObject.__init__(self) | ||
self._friend = friend | ||
self._friend_game = None | ||
friend.newCurrentGame.connect(self._on_new_friend_game) | ||
self._reconnect_game_signals() | ||
|
||
def _on_new_friend_game(self): | ||
self._reconnect_game_signals() | ||
self._check_game_joining_event() | ||
|
||
def _reconnect_game_signals(self): | ||
old_game = self._friend_game | ||
if old_game is not None: | ||
old_game.liveReplayAvailable.disconnect( | ||
self._check_game_replay_event) | ||
|
||
new_game = self._friend.currentGame | ||
self._friend_game = new_game | ||
if new_game is not None: | ||
new_game.liveReplayAvailable.connect( | ||
self._check_game_replay_event) | ||
|
||
def _check_game_joining_event(self): | ||
if self._friend_game is None: | ||
return | ||
if self._friend_game.state == GameState.OPEN: | ||
if self._friend_game.host == self._friend.login: | ||
self.friendEvent.emit(self._friend, FriendEvents.HOSTING_GAME) | ||
else: | ||
self.friendEvent.emit(self._friend, FriendEvents.JOINED_GAME) | ||
|
||
def _check_game_replay_event(self): | ||
if self._friend_game is None: | ||
return | ||
if not self._friend_game.has_live_replay: | ||
return | ||
self.friendEvent.emit(self._friend, FriendEvents.REPLAY_AVAILABLE) | ||
|
||
def report_all_events(self): | ||
self._check_game_joining_event() | ||
self._check_game_replay_event() | ||
|
||
|
||
class FriendsEventTracker(QObject): | ||
""" | ||
Forwards notifications about all online friend players. | ||
FIXME: we duplicate all friend tracker signals here, is there a more | ||
elegant way? Maybe an enum and a single signal? | ||
""" | ||
friendEvent = pyqtSignal(object, object) | ||
|
||
def __init__(self, online_friend_tracker): | ||
QObject.__init__(self) | ||
self._online_friend_tracker = online_friend_tracker | ||
self._friend_event_trackers = {} | ||
|
||
self._online_friend_tracker.friendAdded.connect(self._add_friend) | ||
self._online_friend_tracker.friendRemoved.connect(self._remove_friend) | ||
|
||
for friend in self._online_friend_tracker.friends: | ||
self._add_friend(friend) | ||
|
||
def _add_friend(self, friend): | ||
tracker = FriendEventTracker(friend) | ||
tracker.friendEvent.connect(self.friendEvent.emit) | ||
self._friend_event_trackers[friend.id] = tracker | ||
|
||
# No risk of reporting an event twice - either it didn't happen yet | ||
# so it won't be reported here, or it happened already so it wasn't | ||
# tracked | ||
tracker.report_all_events() | ||
|
||
def _remove_friend(self, friend): | ||
try: | ||
# Signals get disconnected automatically since tracker is | ||
# no longer referenced. | ||
del self._friend_event_trackers[friend.id] | ||
except KeyError: | ||
pass | ||
|
||
|
||
def build_friends_tracker(me, playerset): | ||
online_friend_tracker = OnlineFriendsTracker(me, playerset) | ||
friends_event_tracker = FriendsEventTracker(online_friend_tracker) | ||
return friends_event_tracker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,60 @@ | ||
from PyQt5.QtCore import QTimer | ||
from model.game import GameState | ||
|
||
from fa import maps | ||
|
||
from chat.friendtracker import build_friends_tracker | ||
|
||
class GameAnnouncer: | ||
ANNOUNCE_DELAY_SECS = 35 | ||
|
||
def __init__(self, gameset, me, colors, client): | ||
self._gameset = gameset | ||
def __init__(self, playerset, me, colors, client): | ||
self._me = me | ||
self._colors = colors | ||
self._client = client | ||
|
||
self._gameset.newLobby.connect(self._announce_hosting) | ||
self._gameset.newLiveReplay.connect(self._announce_replay) | ||
self._friends_event_tracker = build_friends_tracker(me, playerset) | ||
self._friends_event_tracker.friendEvent.connect(self._friend_event) | ||
|
||
self.announce_games = True | ||
self.announce_replays = True | ||
self._delayed_host_list = [] | ||
|
||
def _is_friend_host(self, game): | ||
return (game.host_player is not None | ||
and self._me.isFriend(game.host_player.id)) | ||
|
||
def _announce_hosting(self, game): | ||
if not self._is_friend_host(game) or not self.announce_games: | ||
return | ||
announce_delay = QTimer() | ||
announce_delay.setSingleShot(True) | ||
announce_delay.setInterval(self.ANNOUNCE_DELAY_SECS * 1000) | ||
announce_delay.timeout.connect(self._delayed_announce_hosting) | ||
announce_delay.start() | ||
self._delayed_host_list.append((announce_delay, game)) | ||
self._delayed_event_list = [] | ||
self.delay_friend_events = True | ||
|
||
def _delayed_announce_hosting(self): | ||
timer, game = self._delayed_host_list.pop(0) | ||
def _friend_event(self, player, event): | ||
if self.delay_friend_events: | ||
self._delayed_event_list.append((player, event)) | ||
else: | ||
self._friend_announce(player, event) | ||
|
||
if (not self._is_friend_host(game) or | ||
not self.announce_games or | ||
game.state != GameState.OPEN): | ||
return | ||
self._announce(game, "hosting") | ||
def delayed_friend_events(self): | ||
self.delay_friend_events = False | ||
while len(self._delayed_event_list) > 0: | ||
player, event = self._delayed_event_list.pop(0) | ||
self._friend_announce(player, event) | ||
|
||
def _announce_replay(self, game): | ||
if not self._is_friend_host(game) or not self.announce_replays: | ||
def _friend_announce(self, player, event): | ||
if player.currentGame is None: | ||
return | ||
self._announce(game, "playing live") | ||
|
||
def _announce(self, game, activity): | ||
url = game.url(game.host_player.id).toString() | ||
url_color = self._colors.getColor("url") | ||
mapname = maps.getDisplayName(game.mapname) | ||
fmt = 'is {} {}<a style="color:{}" href="{}">{}</a> (on {})' | ||
game = player.currentGame | ||
player_info = "" | ||
if game.state == GameState.OPEN: | ||
if not self.announce_games: # Menu Option Chat | ||
return | ||
if game.featured_mod == "ladder1v1": | ||
activity = "started" | ||
elif player.login == game.host: | ||
activity = "is <font color='GoldenRod'>hosting</font>" | ||
else: | ||
activity = "joined" | ||
player_info = " [{}/{}]".format(game.num_players, game.max_players) | ||
elif game.state == GameState.PLAYING: | ||
if not self.announce_replays: # Menu Option Chat | ||
return | ||
activity = "is playing live" | ||
else: # GameSate.CLOSED | ||
activity = "<font color='Red'>has left the building</font>" | ||
if game.featured_mod == "faf": | ||
modname = "" | ||
else: | ||
modname = game.featured_mod + " " | ||
msg = fmt.format(activity, modname, url_color, url, game.title, mapname) | ||
self._client.forwardLocalBroadcast(game.host, msg) | ||
url_color = self._colors.getColor("url") | ||
url = game.url(player.id).toString() | ||
fmt = '{} {}<a style="color:{}" href="{}">{}</a> (on {} {})' | ||
msg = fmt.format(activity, modname, url_color, url, game.title, game.mapdisplayname, player_info) | ||
self._client.forwardLocalBroadcast(player.login, msg) |