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
add player-info (and runtime-info on client start)
stash signals on client start until chatter is added
  • Loading branch information
GrotheFAF committed Jan 20, 2018
1 parent b90bffa commit eb2b327
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 58 deletions.
6 changes: 5 additions & 1 deletion src/chat/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,8 @@ def resize_map_column(self):

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

if chatter.player is not None and chatter.player.currentGame is not None:
self.chat_widget.client.game_announcer.delayed_friend_events(chatter.player)

def remove_chatter(self, chatter, server_action=None):
if chatter in self.chatters:
self.nickList.removeRow(self.chatters[chatter].row())
Expand Down
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
19 changes: 1 addition & 18 deletions src/client/_clientwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,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 @@ -296,22 +293,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
103 changes: 64 additions & 39 deletions src/client/gameannouncer.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,86 @@
from PyQt5.QtCore import QTimer
from model.game import GameState

from fa import maps
from chat.friendtracker import build_friends_tracker, FriendEvents
import time


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 = []
self._delayed_event_list = []
self.delay_friend_events = True

def _is_friend_host(self, game):
return (game.host_player is not None
and self._me.isFriend(game.host_player.id))
def _friend_event(self, player, event):
if self.delay_friend_events:
self._delayed_event_list.append((player, event))
else:
self._friend_announce(player, event)

def _announce_hosting(self, game):
if not self._is_friend_host(game) or not self.announce_games:
def delayed_friend_events(self, player):
if not self.delay_friend_events:
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))

def _delayed_announce_hosting(self):
timer, game = self._delayed_host_list.pop(0)

if (not self._is_friend_host(game) or
not self.announce_games or
game.state != GameState.OPEN):
if len(self._delayed_event_list) == 0:
self.delay_friend_events = False
return
self._announce(game, "hosting")
i = 0
for event in self._delayed_event_list:
if player in event:
player, event = self._delayed_event_list.pop(i)
self._friend_announce(player, event)
i += 1

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
game = player.currentGame
if event == FriendEvents.HOSTING_GAME:
if not self.announce_games: # Menu Option Chat
return
if game.featured_mod == "ladder1v1":
activity = "started"
else:
activity = "is <font color='GoldenRod'>hosting</font>"
elif event == FriendEvents.JOINED_GAME:
if not self.announce_games: # Menu Option Chat
return
if game.featured_mod == "ladder1v1":
activity = "started"
else:
activity = "joined"
elif event == FriendEvents.REPLAY_AVAILABLE:
if not self.announce_replays: # Menu Option Chat
return
activity = "is playing live"
else: # that shouldn't happen
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 {})'
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)
if game.featured_mod != "ladder1v1":
player_info = " [{}/{}]".format(game.num_players, game.max_players)
else:
player_info = ""
time_info = ""
if game.has_live_replay:
time_running = time.time() - game.launched_at
if time_running > 6 * 60: # already running games on client start
time_format = '%M:%S' if time_running < 60 * 60 else '%H:%M:%S'
time_info = " runs {}"\
.format(time.strftime(time_format, time.gmtime(time_running)))
url_color = self._colors.getColor("url")
url = game.url(player.id).toString()

fmt = '{} {}<a style="color:{}" href="{}">{}</a> ' \
'(on <font color="GoldenRod">{}</font> {}{})'
msg = fmt.format(activity, modname, url_color, url, game.title,
game.mapdisplayname, player_info, time_info)
self._client.forwardLocalBroadcast(player.login, msg)

0 comments on commit eb2b327

Please sign in to comment.