Skip to content

Commit

Permalink
add all friends to announcements
Browse files Browse the repository at this point in the history
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
GrotheFAF committed Jan 1, 2018
1 parent 1263c12 commit da7bf6f
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 61 deletions.
157 changes: 157 additions & 0 deletions src/chat/friendtracker.py
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
23 changes: 4 additions & 19 deletions src/client/_clientwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ def __init__(self, *args, **kwargs):
self.players = Playerset() # Players known to the client

self.gameset = Gameset(self.players)
fa.instance.gameset = self.gameset # FIXME

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

self.player_colors = PlayerColors(self.me)

self.game_announcer = GameAnnouncer(self.gameset, self.me,
self.player_colors, self)

self.power = 0 # current user power
self.id = 0
# Initialize the Menu Bar according to settings etc.
Expand All @@ -310,22 +306,8 @@ def __init__(self, *args, **kwargs):
self.modMenu = None

self._alias_window = AliasSearchWindow(self)
#self.nFrame = NewsFrame()
#self.whatsNewLayout.addWidget(self.nFrame)
#self.nFrame.collapse()

#self.nFrame = NewsFrame()
#self.whatsNewLayout.addWidget(self.nFrame)

#self.nFrame = NewsFrame()
#self.whatsNewLayout.addWidget(self.nFrame)


#self.WPApi = WPAPI(self)
#self.WPApi.newsDone.connect(self.on_wpapi_done)
#self.WPApi.download()

#self.controlsContainerLayout.setAlignment(self.pageControlFrame, QtCore.Qt.AlignRight)
self.game_announcer = GameAnnouncer(self.players, self.me, self.player_colors, self)

@property
def state(self):
Expand Down Expand Up @@ -1062,6 +1044,9 @@ def _tabChanged(self, tab, curr, prev):
def mainTabChanged(self, curr):
self._tabChanged(self.mainTabs, curr, self._main_tab)
self._main_tab = curr
# the tab change works after client has kinda initialized
if self.game_announcer.delay_friend_events:
self.game_announcer.delayed_friend_events()

@QtCore.pyqtSlot(int)
def vaultTabChanged(self, curr):
Expand Down
83 changes: 41 additions & 42 deletions src/client/gameannouncer.py
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)

0 comments on commit da7bf6f

Please sign in to comment.