Mercurial > libervia-backend
comparison src/plugins/plugin_misc_room_game.py @ 1367:f71a0fc26886
merged branch frontends_multi_profiles
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 18 Mar 2015 10:52:28 +0100 |
parents | 83127a4c89ce |
children | 069ad98b360d |
comparison
equal
deleted
inserted
replaced
1295:1e3b1f9ad6e2 | 1367:f71a0fc26886 |
---|---|
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 from sat.core.constants import Const as C | 21 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 log = getLogger(__name__) | 23 log = getLogger(__name__) |
24 from twisted.words.protocols.jabber.jid import JID | 24 from twisted.words.protocols.jabber import jid |
25 from twisted.words.xish import domish | 25 from twisted.words.xish import domish |
26 from twisted.internet import defer | 26 from twisted.internet import defer |
27 from time import time | 27 from time import time |
28 from wokkel import disco, iwokkel | 28 from wokkel import disco, iwokkel |
29 from zope.interface import implements | 29 from zope.interface import implements |
49 | 49 |
50 | 50 |
51 class RoomGame(object): | 51 class RoomGame(object): |
52 """This class is used to help launching a MUC game. | 52 """This class is used to help launching a MUC game. |
53 | 53 |
54 Bridge methods callbacks: prepareRoom, playerReady, createGame | 54 Bridge methods callbacks: _prepareRoom, _playerReady, _createGame |
55 Triggered methods: userJoinedTrigger, userLeftTrigger | 55 Triggered methods: userJoinedTrigger, userLeftTrigger |
56 Also called from subclasses: newRound | 56 Also called from subclasses: newRound |
57 | 57 |
58 For examples of messages sequences, please look in sub-classes. | 58 For examples of messages sequences, please look in sub-classes. |
59 """ | 59 """ |
105 if player_init is None: | 105 if player_init is None: |
106 player_init = {} | 106 player_init = {} |
107 self.game_init = game_init | 107 self.game_init = game_init |
108 self.player_init = player_init | 108 self.player_init = player_init |
109 self.games = {} | 109 self.games = {} |
110 self.invitations = {} # list of couple (x, y) with x the time and y a list of users | 110 self.invitations = {} # values are a couple (x, y) with x the time and y a list of users |
111 | 111 |
112 # These are the default settings, which can be overwritten by child class after initialization | 112 # These are the default settings, which can be overwritten by child class after initialization |
113 self.invite_mode = self.FROM_PLAYERS if self.player_init == {} else self.FROM_NONE | 113 self.invite_mode = self.FROM_PLAYERS if self.player_init == {} else self.FROM_NONE |
114 self.wait_mode = self.FOR_NONE if self.player_init == {} else self.FOR_ALL | 114 self.wait_mode = self.FOR_NONE if self.player_init == {} else self.FOR_ALL |
115 self.join_mode = self.INVITED | 115 self.join_mode = self.INVITED |
124 host.trigger.add("MUC user left", self.userLeftTrigger) | 124 host.trigger.add("MUC user left", self.userLeftTrigger) |
125 | 125 |
126 def _createOrInvite(self, room, other_players, profile): | 126 def _createOrInvite(self, room, other_players, profile): |
127 """ | 127 """ |
128 This is called only when someone explicitly wants to play. | 128 This is called only when someone explicitly wants to play. |
129 | |
129 The game will not be created if one already exists in the room, | 130 The game will not be created if one already exists in the room, |
130 also its creation could be postponed until all the expected players | 131 also its creation could be postponed until all the expected players |
131 join the room (in that case it will be created from userJoinedTrigger). | 132 join the room (in that case it will be created from userJoinedTrigger). |
132 @param room: instance of wokkel.muc.Room | 133 @param room (wokkel.muc.Room): the room |
133 @param other_players: list for other players JID userhosts | 134 @param other_players (list[jid.JID]): list of the other players JID (bare) |
134 """ | 135 """ |
135 user_jid = self.host.getJidNStream(profile)[0] | 136 user_jid = self.host.getJidNStream(profile)[0] |
136 room_jid_s = room.occupantJID.userhost() | 137 room_jid = room.occupantJID.userhostJID() |
137 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) | 138 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile) |
138 nicks = [nick] | 139 nicks = [nick] |
139 if self._gameExists(room_jid_s): | 140 if self._gameExists(room_jid): |
140 if not self._checkJoinAuth(room_jid_s, user_jid.userhost(), nick): | 141 if not self._checkJoinAuth(room_jid, user_jid, nick): |
141 return | 142 return |
142 nicks.extend(self._invitePlayers(room, other_players, nick, profile)) | 143 nicks.extend(self._invitePlayers(room, other_players, nick, profile)) |
143 self._updatePlayers(room_jid_s, nicks, True, profile) | 144 self._updatePlayers(room_jid, nicks, True, profile) |
144 else: | 145 else: |
145 self._initGame(room_jid_s, nick) | 146 self._initGame(room_jid, nick) |
146 (auth, waiting, missing) = self._checkWaitAuth(room, other_players) | 147 (auth, waiting, missing) = self._checkWaitAuth(room, other_players) |
147 nicks.extend(waiting) | 148 nicks.extend(waiting) |
148 nicks.extend(self._invitePlayers(room, missing, nick, profile)) | 149 nicks.extend(self._invitePlayers(room, missing, nick, profile)) |
149 if auth: | 150 if auth: |
150 self.createGame(room_jid_s, nicks, profile) | 151 self.createGame(room_jid, nicks, profile) |
151 else: | 152 else: |
152 self._updatePlayers(room_jid_s, nicks, False, profile) | 153 self._updatePlayers(room_jid, nicks, False, profile) |
153 | 154 |
154 def _initGame(self, room_jid_s, referee_nick): | 155 def _initGame(self, room_jid, referee_nick): |
155 """Important: do not add the referee to 'players' yet. For a | 156 """ |
156 <players /> message to be emitted whenever a new player is joining, | 157 |
157 it is necessary to not modify 'players' outside of _updatePlayers. | 158 @param room_jid (jid.JID): JID of the room |
158 """ | 159 @param referee_nick (unicode): nickname of the referee |
159 referee = room_jid_s + '/' + referee_nick | 160 """ |
160 self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False, 'status': {}} | 161 # Important: do not add the referee to 'players' yet. For a |
161 self.games[room_jid_s].update(copy.deepcopy(self.game_init)) | 162 # <players /> message to be emitted whenever a new player is joining, |
162 self.invitations.setdefault(room_jid_s, []) | 163 # it is necessary to not modify 'players' outside of _updatePlayers. |
163 | 164 referee_jid = jid.JID(room_jid.userhost() + '/' + referee_nick) |
164 def _gameExists(self, room_jid_s, started=False): | 165 self.games[room_jid] = {'referee': referee_jid, 'players': [], 'started': False, 'status': {}} |
166 self.games[room_jid].update(copy.deepcopy(self.game_init)) | |
167 self.invitations.setdefault(room_jid, []) | |
168 | |
169 def _gameExists(self, room_jid, started=False): | |
165 """Return True if a game has been initialized/started. | 170 """Return True if a game has been initialized/started. |
166 @param started: if False, the game must be initialized to return True, | 171 @param started: if False, the game must be initialized to return True, |
167 otherwise it must be initialized and started with createGame. | 172 otherwise it must be initialized and started with createGame. |
168 @return: True if a game is initialized/started in that room""" | 173 @return: True if a game is initialized/started in that room""" |
169 return room_jid_s in self.games and (not started or self.games[room_jid_s]['started']) | 174 return room_jid in self.games and (not started or self.games[room_jid]['started']) |
170 | 175 |
171 def _checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False): | 176 def _checkJoinAuth(self, room_jid, user_jid=None, nick="", verbose=False): |
172 """Checks if this profile is allowed to join the game. | 177 """Checks if this profile is allowed to join the game. |
178 | |
173 The parameter nick is used to check if the user is already | 179 The parameter nick is used to check if the user is already |
174 a player in that game. When this method is called from | 180 a player in that game. When this method is called from |
175 userJoinedTrigger, nick is also used to check the user | 181 userJoinedTrigger, nick is also used to check the user |
176 identity instead of user_jid_s (see TODO comment below). | 182 identity instead of user_jid_s (see TODO comment below). |
177 @param room_jid_s: the room hosting the game | 183 @param room_jid (jid.JID): the JID of the room hosting the game |
178 @param user_jid_s: JID userhost of the user | 184 @param user_jid (jid.JID): JID of the user |
179 @param nick: nick of the user | 185 @param nick (unicode): nick of the user |
180 @return: True if this profile can join the game | 186 @return: True if this profile can join the game |
181 """ | 187 """ |
182 auth = False | 188 auth = False |
183 if not self._gameExists(room_jid_s): | 189 if not self._gameExists(room_jid): |
184 auth = False | 190 auth = False |
185 elif self.join_mode == self.ALL or self.isPlayer(room_jid_s, nick): | 191 elif self.join_mode == self.ALL or self.isPlayer(room_jid, nick): |
186 auth = True | 192 auth = True |
187 elif self.join_mode == self.INVITED: | 193 elif self.join_mode == self.INVITED: |
188 user_jid_s = JID(user_jid_s).userhost() | |
189 # considering all the batches of invitations | 194 # considering all the batches of invitations |
190 for invitations in self.invitations[room_jid_s]: | 195 for invitations in self.invitations[room_jid]: |
191 if user_jid_s is not None: | 196 if user_jid is not None: |
192 if user_jid_s in invitations[1]: | 197 if user_jid.userhostJID() in invitations[1]: |
193 auth = True | 198 auth = True |
194 break | 199 break |
195 else: | 200 else: |
196 # TODO: that's not secure enough but what to do if | 201 # TODO: that's not secure enough but what to do if |
197 # wokkel.muc.User's 'entity' attribute is not set?! | 202 # wokkel.muc.User's 'entity' attribute is not set?! |
198 if nick in [JID(invited).user for invited in invitations[1]]: | 203 if nick in [invited.user for invited in invitations[1]]: |
199 auth = True | 204 auth = True |
200 break | 205 break |
201 | 206 |
202 if not auth and (verbose or _DEBUG): | 207 if not auth and (verbose or _DEBUG): |
203 log.debug(_("%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid_s or nick, 'game': self.name, 'room': room_jid_s}) | 208 log.debug(_("%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid.userhost() or nick, 'game': self.name, 'room': room_jid.userhost()}) |
204 return auth | 209 return auth |
205 | 210 |
206 def _updatePlayers(self, room_jid_s, nicks, sync, profile): | 211 def _updatePlayers(self, room_jid, nicks, sync, profile): |
207 """Update the list of players and signal to the room that some players joined the game. | 212 """Update the list of players and signal to the room that some players joined the game. |
208 If sync is True, the news players are synchronized with the game data they have missed. | 213 If sync is True, the news players are synchronized with the game data they have missed. |
209 Remark: self.games[room_jid_s]['players'] should not be modified outside this method. | 214 Remark: self.games[room_jid]['players'] should not be modified outside this method. |
210 @param room_jid_s: room userhost | 215 @param room_jid (jid.JID): JID of the room |
211 @param nicks: list of players nicks in the room (referee included, in first position) | 216 @param nicks (list[unicode]): list of players nicks in the room (referee included, in first position) |
212 @param sync: set to True to send synchronization data to the new players | 217 @param sync (bool): set to True to send synchronization data to the new players |
213 @param profile | 218 @param profile (unicode): %(doc_profile)s |
214 """ | 219 """ |
215 if nicks == []: | 220 if nicks == []: |
216 return | 221 return |
217 # this is better than set(nicks).difference(...) as it keeps the order | 222 # this is better than set(nicks).difference(...) as it keeps the order |
218 new_nicks = [nick for nick in nicks if nick not in self.games[room_jid_s]['players']] | 223 new_nicks = [nick for nick in nicks if nick not in self.games[room_jid]['players']] |
219 if len(new_nicks) == 0: | 224 if len(new_nicks) == 0: |
220 return | 225 return |
221 | 226 |
222 def setStatus(status): | 227 def setStatus(status): |
223 for nick in new_nicks: | 228 for nick in new_nicks: |
224 self.games[room_jid_s]['status'][nick] = status | 229 self.games[room_jid]['status'][nick] = status |
225 | 230 |
226 sync = sync and self._gameExists(room_jid_s, True) and len(self.games[room_jid_s]['players']) > 0 | 231 sync = sync and self._gameExists(room_jid, True) and len(self.games[room_jid]['players']) > 0 |
227 setStatus('desync' if sync else 'init') | 232 setStatus('desync' if sync else 'init') |
228 self.games[room_jid_s]['players'].extend(new_nicks) | 233 self.games[room_jid]['players'].extend(new_nicks) |
229 self._synchronizeRoom(room_jid_s, [JID(room_jid_s)], profile) | 234 self._synchronizeRoom(room_jid, [room_jid], profile) |
230 if sync: | 235 if sync: |
231 setStatus('init') | 236 setStatus('init') |
232 | 237 |
233 def _synchronizeRoom(self, room_jid_s, recipients, profile): | 238 def _synchronizeRoom(self, room_jid, recipients, profile): |
234 """Communicate the list of players to the whole room or only to some users, | 239 """Communicate the list of players to the whole room or only to some users, |
235 also send the synchronization data to the players who recently joined the game. | 240 also send the synchronization data to the players who recently joined the game. |
236 @param room_jid_s: room userhost | 241 @param room_jid (jid.JID): JID of the room |
237 @recipients: list of JIDs, the recipients of the message could be: | 242 @recipients (list[jid.JID]): list of JIDs, the recipients of the message could be: |
238 - room JID | 243 - room JID |
239 - room JID + "/" + user nick | 244 - room JID + "/" + user nick |
240 @param profile | 245 @param profile (unicode): %(doc_profile)s |
241 """ | 246 """ |
242 if self._gameExists(room_jid_s, started=True): | 247 if self._gameExists(room_jid, started=True): |
243 element = self._createStartElement(self.games[room_jid_s]['players']) | 248 element = self._createStartElement(self.games[room_jid]['players']) |
244 else: | 249 else: |
245 element = self._createStartElement(self.games[room_jid_s]['players'], name="players") | 250 element = self._createStartElement(self.games[room_jid]['players'], name="players") |
246 elements = [(element, None, None)] | 251 elements = [(element, None, None)] |
247 | 252 |
248 sync_args = [] | 253 sync_args = [] |
249 sync_data = self._getSyncData(room_jid_s) | 254 sync_data = self._getSyncData(room_jid) |
250 for nick in sync_data: | 255 for nick in sync_data: |
251 user_jid = JID(room_jid_s + '/' + nick) | 256 user_jid = jid.JID(room_jid.userhost() + '/' + nick) |
252 if user_jid in recipients: | 257 if user_jid in recipients: |
253 user_elements = copy.deepcopy(elements) | 258 user_elements = copy.deepcopy(elements) |
254 for child in sync_data[nick]: | 259 for child in sync_data[nick]: |
255 user_elements.append((child, None, None)) | 260 user_elements.append((child, None, None)) |
256 recipients.remove(user_jid) | 261 recipients.remove(user_jid) |
261 for recipient in recipients: | 266 for recipient in recipients: |
262 self._sendElements(recipient, elements, profile=profile) | 267 self._sendElements(recipient, elements, profile=profile) |
263 for args, kwargs in sync_args: | 268 for args, kwargs in sync_args: |
264 self._sendElements(*args, **kwargs) | 269 self._sendElements(*args, **kwargs) |
265 | 270 |
266 def _getSyncData(self, room_jid_s, force_nicks=None): | 271 def _getSyncData(self, room_jid, force_nicks=None): |
267 """The synchronization data are returned for each player who | 272 """The synchronization data are returned for each player who |
268 has the state 'desync' or if he's been contained by force_nicks. | 273 has the state 'desync' or if he's been contained by force_nicks. |
269 @param room_jid_s: room userhost | 274 @param room_jid (jid.JID): JID of the room |
270 @param force_nicks: force the synchronization for this list of the nicks | 275 @param force_nicks: force the synchronization for this list of the nicks |
271 @return: a mapping between player nicks and a list of elements to | 276 @return: a mapping between player nicks and a list of elements to |
272 be sent by self._synchronizeRoom for the game to be synchronized. | 277 be sent by self._synchronizeRoom for the game to be synchronized. |
273 """ | 278 """ |
274 if not self._gameExists(room_jid_s): | 279 if not self._gameExists(room_jid): |
275 return {} | 280 return {} |
276 data = {} | 281 data = {} |
277 status = self.games[room_jid_s]['status'] | 282 status = self.games[room_jid]['status'] |
278 nicks = [nick for nick in status if status[nick] == 'desync'] | 283 nicks = [nick for nick in status if status[nick] == 'desync'] |
279 if force_nicks is None: | 284 if force_nicks is None: |
280 force_nicks = [] | 285 force_nicks = [] |
281 for nick in force_nicks: | 286 for nick in force_nicks: |
282 if nick not in nicks: | 287 if nick not in nicks: |
283 nicks.append(nick) | 288 nicks.append(nick) |
284 for nick in nicks: | 289 for nick in nicks: |
285 elements = self.getSyncDataForPlayer(room_jid_s, nick) | 290 elements = self.getSyncDataForPlayer(room_jid, nick) |
286 if elements: | 291 if elements: |
287 data[nick] = elements | 292 data[nick] = elements |
288 return data | 293 return data |
289 | 294 |
290 def getSyncDataForPlayer(self, room_jid_s, nick): | 295 def getSyncDataForPlayer(self, room_jid, nick): |
291 """This method may (and should probably) be overwritten by a child class. | 296 """This method may (and should probably) be overwritten by a child class. |
292 @param room_jid_s: room userhost | 297 @param room_jid (jid.JID): JID of the room |
293 @param nick: the nick of the player to be synchronized | 298 @param nick: the nick of the player to be synchronized |
294 @return: a list of elements to synchronize this player with the game. | 299 @return: a list of elements to synchronize this player with the game. |
295 """ | 300 """ |
296 return [] | 301 return [] |
297 | 302 |
298 def _invitePlayers(self, room, other_players, nick, profile): | 303 def _invitePlayers(self, room, other_players, nick, profile): |
299 """Invite players to a room, associated game may exist or not. | 304 """Invite players to a room, associated game may exist or not. |
300 @param room: wokkel.muc.Room instance | 305 |
301 @param other_players: list of JID userhosts to invite | 306 @param room (wokkel.muc.Room): the room |
302 @param nick: nick of the user who send the invitation | 307 @param other_players (list[jid.JID]): list of the players to invite |
303 @return: list of room nicks for invited players who are already in the room | 308 @param nick (unicode): nick of the user who send the invitation |
309 @return: list[unicode] of room nicks for invited players who are already in the room | |
304 """ | 310 """ |
305 room_jid = room.occupantJID.userhostJID() | 311 room_jid = room.occupantJID.userhostJID() |
306 room_jid_s = room.occupantJID.userhost() | 312 if not self._checkInviteAuth(room_jid, nick): |
307 if not self._checkInviteAuth(room_jid_s, nick): | |
308 return [] | 313 return [] |
309 # TODO: remove invitation waiting for too long, using the time data | 314 # TODO: remove invitation waiting for too long, using the time data |
310 players_jids = [JID(player) for player in other_players] | 315 self.invitations[room_jid].append((time(), [player.userhostJID() for player in other_players])) |
311 self.invitations[room_jid_s].append((time(), [player.userhost() for player in players_jids])) | |
312 nicks = [] | 316 nicks = [] |
313 for player_jid in [player.userhostJID() for player in players_jids]: | 317 for player_jid in [player.userhostJID() for player in other_players]: |
314 # TODO: find a way to make it secure | 318 # TODO: find a way to make it secure |
315 other_nick = self.host.plugins["XEP-0045"].getRoomNickOfUser(room, player_jid, secure=self.testing) | 319 other_nick = self.host.plugins["XEP-0045"].getRoomNickOfUser(room, player_jid, secure=self.testing) |
316 if other_nick is None: | 320 if other_nick is None: |
317 self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile) | 321 self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile) |
318 else: | 322 else: |
319 nicks.append(other_nick) | 323 nicks.append(other_nick) |
320 return nicks | 324 return nicks |
321 | 325 |
322 def _checkInviteAuth(self, room_jid_s, nick, verbose=False): | 326 def _checkInviteAuth(self, room_jid, nick, verbose=False): |
323 """Checks if this user is allowed to invite players | 327 """Checks if this user is allowed to invite players |
324 @param room_jid_s: room userhost | 328 |
329 @param room_jid (jid.JID): JID of the room | |
325 @param nick: user nick in the room | 330 @param nick: user nick in the room |
326 @param verbose: display debug message | 331 @param verbose: display debug message |
327 @return: True if the user is allowed to invite other players | 332 @return: True if the user is allowed to invite other players |
328 """ | 333 """ |
329 auth = False | 334 auth = False |
330 if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid_s): | 335 if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid): |
331 auth = True | 336 auth = True |
332 elif self.invite_mode == self.FROM_NONE: | 337 elif self.invite_mode == self.FROM_NONE: |
333 auth = not self._gameExists(room_jid_s, started=True) and self.isReferee(room_jid_s, nick) | 338 auth = not self._gameExists(room_jid, started=True) and self.isReferee(room_jid, nick) |
334 elif self.invite_mode == self.FROM_REFEREE: | 339 elif self.invite_mode == self.FROM_REFEREE: |
335 auth = self.isReferee(room_jid_s, nick) | 340 auth = self.isReferee(room_jid, nick) |
336 elif self.invite_mode == self.FROM_PLAYERS: | 341 elif self.invite_mode == self.FROM_PLAYERS: |
337 auth = self.isPlayer(room_jid_s, nick) | 342 auth = self.isPlayer(room_jid, nick) |
338 if not auth and (verbose or _DEBUG): | 343 if not auth and (verbose or _DEBUG): |
339 log.debug(_("%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid_s}) | 344 log.debug(_("%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid.userhost()}) |
340 return auth | 345 return auth |
341 | 346 |
342 def isReferee(self, room_jid_s, nick): | 347 def isReferee(self, room_jid, nick): |
343 """Checks if the player with this nick is the referee for the game in this room" | 348 """Checks if the player with this nick is the referee for the game in this room" |
344 @param room_jid_s: room userhost | 349 @param room_jid (jid.JID): room JID |
345 @param nick: user nick in the room | 350 @param nick: user nick in the room |
346 @return: True if the user is the referee of the game in this room | 351 @return: True if the user is the referee of the game in this room |
347 """ | 352 """ |
348 if not self._gameExists(room_jid_s): | 353 if not self._gameExists(room_jid): |
349 return False | 354 return False |
350 return room_jid_s + '/' + nick == self.games[room_jid_s]['referee'] | 355 return jid.JID(room_jid.userhost() + '/' + nick) == self.games[room_jid]['referee'] |
351 | 356 |
352 def isPlayer(self, room_jid_s, nick): | 357 def isPlayer(self, room_jid, nick): |
353 """Checks if the user with this nick is a player for the game in this room. | 358 """Checks if the user with this nick is a player for the game in this room. |
354 @param room_jid_s: room userhost | 359 @param room_jid (jid.JID): JID of the room |
355 @param nick: user nick in the room | 360 @param nick: user nick in the room |
356 @return: True if the user is a player of the game in this room | 361 @return: True if the user is a player of the game in this room |
357 """ | 362 """ |
358 if not self._gameExists(room_jid_s): | 363 if not self._gameExists(room_jid): |
359 return False | 364 return False |
360 # Important: the referee is not in the 'players' list right after | 365 # Important: the referee is not in the 'players' list right after |
361 # the game initialization, that's why we do also check with isReferee | 366 # the game initialization, that's why we do also check with isReferee |
362 return nick in self.games[room_jid_s]['players'] or self.isReferee(room_jid_s, nick) | 367 return nick in self.games[room_jid]['players'] or self.isReferee(room_jid, nick) |
363 | 368 |
364 def _checkWaitAuth(self, room, other_players, verbose=False): | 369 def _checkWaitAuth(self, room, other_players, verbose=False): |
365 """Check if we must wait for other players before starting the game. | 370 """Check if we must wait for other players before starting the game. |
366 | 371 |
367 @param room: wokkel.muc.Room instance | 372 @param room (wokkel.muc.Room): the room |
368 @param other_players: list of players JID userhosts without the referee | 373 @param other_players (list[jid.JID]): list of the players without the referee |
369 @param verbose: display debug message | 374 @param verbose (bool): display debug message |
370 @return: (x, y, z) with: | 375 @return: (x, y, z) with: |
371 x: False if we must wait, True otherwise | 376 x: False if we must wait, True otherwise |
372 y: the nicks of the players that have been checked and confirmed | 377 y: the nicks of the players that have been checked and confirmed |
373 z: the players that have not been checked or that are missing | 378 z: the JID of the players that have not been checked or that are missing |
374 """ | 379 """ |
375 if self.wait_mode == self.FOR_NONE or other_players == []: | 380 if self.wait_mode == self.FOR_NONE or other_players == []: |
376 result = (True, [], other_players) | 381 result = (True, [], other_players) |
377 elif len(room.roster) < len(other_players): | 382 elif len(room.roster) < len(other_players): |
378 # do not check the players until we may actually have them all | 383 # do not check the players until we may actually have them all |
386 return result | 391 return result |
387 | 392 |
388 def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE): | 393 def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE): |
389 """Generate unique room name | 394 """Generate unique room name |
390 | 395 |
391 @param muc_service: you can leave empty to autofind the muc service | 396 @param muc_service (jid.JID): you can leave empty to autofind the muc service |
392 @param profile_key: %(doc_profile_key)s | 397 @param profile_key (unicode): %(doc_profile_key)s |
393 @return: a unique name for a new room to be created | 398 @return: jid.JID (unique name for a new room to be created) |
394 """ | 399 """ |
395 # FIXME: jid.JID must be used instead of strings | 400 # FIXME: jid.JID must be used instead of strings |
396 room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key) | 401 room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key) |
397 return "sat_%s_%s" % (self.name.lower(), room.full()) | 402 return jid.JID("sat_%s_%s" % (self.name.lower(), room.userhost())) |
398 | 403 |
399 def prepareRoom(self, other_players=None, room_jid_s=None, profile_key=C.PROF_KEY_NONE): | 404 def _prepareRoom(self, other_players=None, room_jid_s='', profile_key=C.PROF_KEY_NONE): |
405 room_jid = jid.JID(room_jid_s) if room_jid_s else None | |
406 other_players = [jid.JID(player).userhostJID() for player in other_players] | |
407 return self.prepareRoom(other_players, room_jid, profile_key) | |
408 | |
409 def prepareRoom(self, other_players=None, room_jid=None, profile_key=C.PROF_KEY_NONE): | |
400 """Prepare the room for a game: create it if it doesn't exist and invite players. | 410 """Prepare the room for a game: create it if it doesn't exist and invite players. |
401 | 411 |
402 @param other_players: list for other players JID userhosts | 412 @param other_players (list[JID]): list of other players JID (bare) |
403 @param room_jid_s: JID userhost of the room, or None to generate a unique name | 413 @param room_jid (jid.JID): JID of the room, or None to generate a unique name |
404 @param profile_key | 414 @param profile_key (unicode): %(doc_profile_key)s |
405 """ | 415 """ |
406 log.debug(_('Preparing room for %s game') % self.name) | 416 log.debug(_('Preparing room for %s game') % self.name) |
407 profile = self.host.memory.getProfileName(profile_key) | 417 profile = self.host.memory.getProfileName(profile_key) |
408 if not profile: | 418 if not profile: |
409 log.error(_("Unknown profile")) | 419 log.error(_("Unknown profile")) |
411 if other_players is None: | 421 if other_players is None: |
412 other_players = [] | 422 other_players = [] |
413 | 423 |
414 def roomJoined(room): | 424 def roomJoined(room): |
415 """@param room: instance of wokkel.muc.Room""" | 425 """@param room: instance of wokkel.muc.Room""" |
416 self._createOrInvite(room, [JID(player).userhost() for player in other_players], profile) | 426 self._createOrInvite(room, other_players, profile) |
417 | 427 |
418 # Create/join the given room, or a unique generated one if no room is specified. | 428 # Create/join the given room, or a unique generated one if no room is specified. |
419 if room_jid_s is not None and room_jid_s != "": # a room name has been specified | 429 if room_jid is None: |
420 if room_jid_s in self.host.plugins["XEP-0045"].clients[profile].joined_rooms: | 430 room_jid = self.getUniqueName(profile_key=profile_key) |
421 roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid_s]) | 431 else: |
432 if room_jid in self.host.plugins["XEP-0045"].clients[profile].joined_rooms: | |
433 roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid]) | |
422 return defer.succeed(None) | 434 return defer.succeed(None) |
423 else: | 435 |
424 room_jid_s = self.getUniqueName(profile_key=profile_key) | |
425 if room_jid_s == "": | |
426 return defer.succeed(None) | |
427 user_jid = self.host.getJidNStream(profile)[0] | 436 user_jid = self.host.getJidNStream(profile)[0] |
428 d = self.host.plugins["XEP-0045"].join(JID(room_jid_s), user_jid.user, {}, profile) | 437 d = self.host.plugins["XEP-0045"].join(room_jid, user_jid.user, {}, profile) |
429 return d.addCallback(roomJoined) | 438 return d.addCallback(roomJoined) |
430 | 439 |
431 def userJoinedTrigger(self, room, user, profile): | 440 def userJoinedTrigger(self, room, user, profile): |
432 """This trigger is used to check if the new user can take part of a game, create the game if we were waiting for him or just update the players list. | 441 """This trigger is used to check if the new user can take part of a game, create the game if we were waiting for him or just update the players list. |
433 | 442 |
434 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} | 443 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} |
435 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID | 444 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID |
436 @return: True to not interrupt the main process. | 445 @return: True to not interrupt the main process. |
437 """ | 446 """ |
438 room_jid_s = room.occupantJID.userhost() | 447 room_jid = room.occupantJID.userhostJID() |
439 profile_nick = room.occupantJID.resource | 448 profile_nick = room.occupantJID.resource |
440 if not self.isReferee(room_jid_s, profile_nick): | 449 if not self.isReferee(room_jid, profile_nick): |
441 return True # profile is not the referee | 450 return True # profile is not the referee |
442 if not self._checkJoinAuth(room_jid_s, user.entity.userhost() if user.entity else None, user.nick): | 451 if not self._checkJoinAuth(room_jid, user.entity if user.entity else None, user.nick): |
443 # user not allowed but let him know that we are playing :p | 452 # user not allowed but let him know that we are playing :p |
444 self._synchronizeRoom(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile) | 453 self._synchronizeRoom(room_jid, [jid.JID(room_jid.userhost() + '/' + user.nick)], profile) |
445 return True | 454 return True |
446 if self.wait_mode == self.FOR_ALL: | 455 if self.wait_mode == self.FOR_ALL: |
447 # considering the last batch of invitations | 456 # considering the last batch of invitations |
448 batch = len(self.invitations[room_jid_s]) - 1 | 457 batch = len(self.invitations[room_jid]) - 1 |
449 if batch < 0: | 458 if batch < 0: |
450 log.error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid_s)) | 459 log.error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid.userhost())) |
451 return True | 460 return True |
452 other_players = self.invitations[room_jid_s][batch][1] | 461 other_players = self.invitations[room_jid][batch][1] |
453 (auth, nicks, dummy) = self._checkWaitAuth(room, other_players) | 462 (auth, nicks, dummy) = self._checkWaitAuth(room, other_players) |
454 if auth: | 463 if auth: |
455 del self.invitations[room_jid_s][batch] | 464 del self.invitations[room_jid][batch] |
456 nicks.insert(0, profile_nick) # add the referee | 465 nicks.insert(0, profile_nick) # add the referee |
457 self.createGame(room_jid_s, nicks, profile_key=profile) | 466 self.createGame(room_jid, nicks, profile_key=profile) |
458 return True | 467 return True |
459 # let the room know that a new player joined | 468 # let the room know that a new player joined |
460 self._updatePlayers(room_jid_s, [user.nick], True, profile) | 469 self._updatePlayers(room_jid, [user.nick], True, profile) |
461 return True | 470 return True |
462 | 471 |
463 def userLeftTrigger(self, room, user, profile): | 472 def userLeftTrigger(self, room, user, profile): |
464 """This trigger is used to update or stop the game when a user leaves. | 473 """This trigger is used to update or stop the game when a user leaves. |
465 | 474 |
466 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} | 475 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} |
467 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID | 476 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID |
468 @return: True to not interrupt the main process. | 477 @return: True to not interrupt the main process. |
469 """ | 478 """ |
470 room_jid_s = room.occupantJID.userhost() | 479 room_jid = room.occupantJID.userhostJID() |
471 profile_nick = room.occupantJID.resource | 480 profile_nick = room.occupantJID.resource |
472 if not self.isReferee(room_jid_s, profile_nick): | 481 if not self.isReferee(room_jid, profile_nick): |
473 return True # profile is not the referee | 482 return True # profile is not the referee |
474 if self.isPlayer(room_jid_s, user.nick): | 483 if self.isPlayer(room_jid, user.nick): |
475 try: | 484 try: |
476 self.games[room_jid_s]['players'].remove(user.nick) | 485 self.games[room_jid]['players'].remove(user.nick) |
477 except ValueError: | 486 except ValueError: |
478 pass | 487 pass |
479 if len(self.games[room_jid_s]['players']) == 0: | 488 if len(self.games[room_jid]['players']) == 0: |
480 del self.games[room_jid_s] # finish the game | |
481 return True | 489 return True |
482 if self.wait_mode == self.FOR_ALL: | 490 if self.wait_mode == self.FOR_ALL: |
483 # allow this user to join the game again | 491 # allow this user to join the game again |
484 user_jid_s = user.entity.userhost() | 492 user_jid = user.entity.userhostJID() |
485 if len(self.invitations[room_jid_s]) == 0: | 493 if len(self.invitations[room_jid]) == 0: |
486 self.invitations[room_jid_s].append((time(), [user_jid_s])) | 494 self.invitations[room_jid].append((time(), [user_jid])) |
487 else: | 495 else: |
488 batch = 0 # add to the first batch of invitations | 496 batch = 0 # add to the first batch of invitations |
489 if user_jid_s not in self.invitations[room_jid_s][batch][1]: | 497 if user_jid not in self.invitations[room_jid][batch][1]: |
490 self.invitations[room_jid_s][batch][1].append(user_jid_s) | 498 self.invitations[room_jid][batch][1].append(user_jid) |
491 return True | 499 return True |
492 | 500 |
493 def _checkCreateGameAndInit(self, room_jid_s, profile): | 501 def _checkCreateGameAndInit(self, room_jid, profile): |
494 """Check if that profile can create the game. If the game can be created but is not initialized yet, this method will also do the initialization. | 502 """Check if that profile can create the game. If the game can be created |
495 | 503 but is not initialized yet, this method will also do the initialization. |
496 @param room_jid_s: room userhost | 504 |
505 @param room_jid (jid.JID): JID of the room | |
497 @param profile | 506 @param profile |
498 @return: a couple (create, sync) with: | 507 @return: a couple (create, sync) with: |
499 - create: set to True to allow the game creation | 508 - create: set to True to allow the game creation |
500 - sync: set to True to advice a game synchronization | 509 - sync: set to True to advice a game synchronization |
501 """ | 510 """ |
502 user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) | 511 user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile) |
503 if not user_nick: | 512 if not user_nick: |
504 log.error('Internal error: profile %s has not joined the room %s' % (profile, room_jid_s)) | 513 log.error('Internal error: profile %s has not joined the room %s' % (profile, room_jid.userhost())) |
505 return False, False | 514 return False, False |
506 if self._gameExists(room_jid_s): | 515 if self._gameExists(room_jid): |
507 is_referee = self.isReferee(room_jid_s, user_nick) | 516 is_referee = self.isReferee(room_jid, user_nick) |
508 if self._gameExists(room_jid_s, started=True): | 517 if self._gameExists(room_jid, started=True): |
509 log.warning(_("%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid_s}) | 518 log.warning(_("%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid.userhost()}) |
510 return False, is_referee | 519 return False, is_referee |
511 elif not is_referee: | 520 elif not is_referee: |
512 log.warning(_("%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid_s, 'user': user_nick}) | 521 log.warning(_("%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid.userhost(), 'user': user_nick}) |
513 return False, False | 522 return False, False |
514 else: | 523 else: |
515 self._initGame(room_jid_s, user_nick) | 524 self._initGame(room_jid, user_nick) |
516 return True, False | 525 return True, False |
517 | 526 |
518 def createGame(self, room_jid_s, nicks=None, profile_key=C.PROF_KEY_NONE): | 527 def _createGame(self, room_jid_s, nicks=None, profile_key=C.PROF_KEY_NONE): |
528 self.createGame(jid.JID(room_jid_s), nicks, profile_key) | |
529 | |
530 def createGame(self, room_jid, nicks=None, profile_key=C.PROF_KEY_NONE): | |
519 """Create a new game. | 531 """Create a new game. |
520 | 532 |
521 This can be called directly from a frontend and skips all the checks and invitation system, | 533 This can be called directly from a frontend and skips all the checks and invitation system, |
522 but the game must not exist and all the players must be in the room already. | 534 but the game must not exist and all the players must be in the room already. |
523 @param room_jid: JID userhost of the room | 535 @param room_jid (jid.JID): JID of the room |
524 @param nicks: list of players nicks in the room (referee included, in first position) | 536 @param nicks (list[unicode]): list of players nicks in the room (referee included, in first position) |
525 @param profile_key: %(doc_profile_key)s | 537 @param profile_key (unicode): %(doc_profile_key)s |
526 """ | 538 """ |
527 log.debug(_("Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid_s}) | 539 log.debug(_("Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid}) |
528 profile = self.host.memory.getProfileName(profile_key) | 540 profile = self.host.memory.getProfileName(profile_key) |
529 if not profile: | 541 if not profile: |
530 log.error(_("profile %s is unknown") % profile_key) | 542 log.error(_("profile %s is unknown") % profile_key) |
531 return | 543 return |
532 (create, sync) = self._checkCreateGameAndInit(room_jid_s, profile) | 544 (create, sync) = self._checkCreateGameAndInit(room_jid, profile) |
533 if nicks is None: | 545 if nicks is None: |
534 nicks = [] | 546 nicks = [] |
535 if not create: | 547 if not create: |
536 if sync: | 548 if sync: |
537 self._updatePlayers(room_jid_s, nicks, True, profile) | 549 self._updatePlayers(room_jid, nicks, True, profile) |
538 return | 550 return |
539 self.games[room_jid_s]['started'] = True | 551 self.games[room_jid]['started'] = True |
540 self._updatePlayers(room_jid_s, nicks, False, profile) | 552 self._updatePlayers(room_jid, nicks, False, profile) |
541 if self.player_init: | 553 if self.player_init: |
542 # specific data to each player (score, private data) | 554 # specific data to each player (score, private data) |
543 self.games[room_jid_s].setdefault('players_data', {}) | 555 self.games[room_jid].setdefault('players_data', {}) |
544 for nick in nicks: | 556 for nick in nicks: |
545 # The dict must be COPIED otherwise it is shared between all users | 557 # The dict must be COPIED otherwise it is shared between all users |
546 self.games[room_jid_s]['players_data'][nick] = copy.deepcopy(self.player_init) | 558 self.games[room_jid]['players_data'][nick] = copy.deepcopy(self.player_init) |
547 | 559 |
548 def playerReady(self, player, referee, profile_key=C.PROF_KEY_NONE): | 560 def _playerReady(self, player_nick, referee_jid_s, profile_key=C.PROF_KEY_NONE): |
561 self.playerReady(player_nick, jid.JID(referee_jid_s), profile_key) | |
562 | |
563 def playerReady(self, player_nick, referee_jid, profile_key=C.PROF_KEY_NONE): | |
549 """Must be called when player is ready to start a new game | 564 """Must be called when player is ready to start a new game |
550 | 565 |
551 @param player: the player nick in the room | 566 @param player: the player nick in the room |
552 @param referee: referee userhost | 567 @param referee_jid (jid.JID): JID of the referee |
553 """ | 568 """ |
554 profile = self.host.memory.getProfileName(profile_key) | 569 profile = self.host.memory.getProfileName(profile_key) |
555 if not profile: | 570 if not profile: |
556 log.error(_("profile %s is unknown") % profile_key) | 571 log.error(_("profile %s is unknown") % profile_key) |
557 return | 572 return |
558 log.debug('new player ready: %s' % profile) | 573 log.debug('new player ready: %s' % profile) |
559 # TODO: we probably need to add the game and room names in the sent message | 574 # TODO: we probably need to add the game and room names in the sent message |
560 self.send(JID(referee), 'player_ready', {'player': player}, profile=profile) | 575 self.send(referee_jid, 'player_ready', {'player': player_nick}, profile=profile) |
561 | 576 |
562 def newRound(self, room_jid, data, profile): | 577 def newRound(self, room_jid, data, profile): |
563 """Launch a new round (reinit the user data) | 578 """Launch a new round (reinit the user data) |
564 | 579 |
565 @param room_jid: room userhost | 580 @param room_jid: room userhost |
567 - common_data: backend initialization data for the new round | 582 - common_data: backend initialization data for the new round |
568 - msg_elts: dict to map each user to his specific initialization message | 583 - msg_elts: dict to map each user to his specific initialization message |
569 @param profile | 584 @param profile |
570 """ | 585 """ |
571 log.debug(_('new round for %s game') % self.name) | 586 log.debug(_('new round for %s game') % self.name) |
572 game_data = self.games[room_jid.userhost()] | 587 game_data = self.games[room_jid] |
573 players = game_data['players'] | 588 players = game_data['players'] |
574 players_data = game_data['players_data'] | 589 players_data = game_data['players_data'] |
575 game_data['stage'] = "init" | 590 game_data['stage'] = "init" |
576 | 591 |
577 common_data, msg_elts = copy.deepcopy(data) if data is not None else (None, None) | 592 common_data, msg_elts = copy.deepcopy(data) if data is not None else (None, None) |
578 | 593 |
579 if isinstance(msg_elts, dict): | 594 if isinstance(msg_elts, dict): |
580 for player in players: | 595 for player in players: |
581 to_jid = JID(room_jid.userhost() + "/" + player) # FIXME: gof: | 596 to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof: |
582 elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None | 597 elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None |
583 self.send(to_jid, elem, profile=profile) | 598 self.send(to_jid, elem, profile=profile) |
584 elif isinstance(msg_elts, domish.Element): | 599 elif isinstance(msg_elts, domish.Element): |
585 self.send(room_jid, msg_elts, profile=profile) | 600 self.send(room_jid, msg_elts, profile=profile) |
586 if common_data is not None: | 601 if common_data is not None: |