view src/tools/plugins/games.py @ 714:ecc5a5b34ee1

plugins (games): add a method to send messages more easily
author souliane <souliane@mailoo.org>
date Mon, 18 Nov 2013 14:25:40 +0100
parents f610864eb7a5
children 358018c5c398
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

# SAT: a jabber client
# Copyright (C) 2009, 2010, 2011, 2012, 2013  Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from logging import debug, warning, error
from twisted.words.protocols.jabber.jid import JID
from twisted.words.xish import domish
from time import time
"""This library help manage general games (e.g. card games) and it can be used by plugins"""


class RoomGame(object):
    """This class is used to help launching a MUC game."""

    def __init__(self, host, plugin_info, ns_tag, player_init_data={}, options={}):
        """
        @param host
        @param plugin_info: PLUGIN_INFO map of the game plugin
        @ns_tag: couple (nameservice, tag) to construct the messages
        @param player_init_data: dictionary for initialization data, applicable to each player
        @param options: dictionary for game options
        """
        self.host = host
        self.name = plugin_info["import_name"]
        self.ns_tag = ns_tag
        self.player_init_data = player_init_data
        self.collectiveGame = self.player_init_data == {}
        self.options = options
        self.games = {}
        self.waiting_inv = {}  # Invitation waiting for people to join to launch a game

    def getUniqueName(self, muc_service="", profile_key='@DEFAULT@'):
        room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key)
        return "sat_%s_%s" % (self.name.lower(), room) if room != "" else ""

    def prepareRoom(self, other_players, room_jid=None, profile_key='@NONE@'):
        """Prepare the room for a game: create it and invite players.
        @param other_players: list for other players JID userhosts
        @param room_jid: JID of the room to reuse or None to create a new room
        """
        debug(_('Preparing room for %s game') % self.name)
        profile = self.host.memory.getProfileName(profile_key)
        if not profile:
            error(_("Unknown profile"))
            return
        _jid, xmlstream = self.host.getJidNStream(profile)
        if other_players is None:
            other_players = []
        players = other_players[:]
        players.append(_jid.userhost())

        def roomJoined(room):
            """@param room: instance of wokkel.muc.Room"""
            _room = room.occupantJID.userhostJID()
            if self.collectiveGame is True or other_players == [] and _jid in [user.entity for user in room.roster.values()]:
                self.createGame(_room.userhost(), [] if self.collectiveGame is True else players, profile_key=profile)
            else:
                self.waiting_inv[_room] = (time(), players)  # TODO: remove invitation waiting for too long, using the time data
            for player in other_players:
                self.host.plugins["XEP-0249"].invite(JID(player), room.occupantJID.userhostJID(), {"game": self.name}, profile)

        def after_init(room_jid):
            if room_jid is not None and room_jid != "":
                # a room name has been specified...
                if room_jid in self.host.plugins["XEP-0045"].clients[profile].joined_rooms:
                    # and we're already in
                    roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid])
                    return
            else:
                room_jid = self.getUniqueName(profile_key=profile_key)
                if room_jid == "":
                    return
            d = self.host.plugins["XEP-0045"].join(JID(room_jid), _jid.user, {}, profile)
            d.addCallback(roomJoined)

        client = self.host.getClient(profile)
        if not client:
            error(_('No client for this profile key: %s') % profile_key)
            return
        client.client_initialized.addCallback(lambda ignore: after_init(room_jid))

    def userJoinedTrigger(self, room, user, profile):
        """This trigger is used to check if we are waiting for people in this room,
        and to create a game if everybody is here.
        @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User}
        @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID
        """
        _room_jid = room.occupantJID.userhostJID()
        if self.collectiveGame is True:
            room_s = _room_jid.userhost()
            if room_s in self.games and self.games[room_s]["referee"] == room.occupantJID.full():
                #we are in a radiocol room, let's start the party !
                self.send(JID(room_s + '/' + user.nick), self.createStartElement(), profile=profile)
            return True
        if _room_jid in self.waiting_inv and len(room.roster) >= len(self.waiting_inv[_room_jid][1]):
            expected_players = self.waiting_inv[_room_jid][1]
            players = []
            for player in expected_players:
                for user in room.roster.values():
                    if user.entity is not None:
                        # check people identity
                        if user.entity.userhost() == player:
                            players.append(user.nick)
                        continue
                    else:
                        # TODO: how to check the identity with only a nickname??
                        if user.nick == JID(player).user:
                            players.append(user.nick)
            if len(players) < len(expected_players):
                # Someone here was not invited! He can stay but he doesn't play :p
                return True
            # When we have all people in the room, we create the game
            del self.waiting_inv[_room_jid]
            self.createGame(_room_jid.userhost(), players, profile_key=profile)
        return True

    def createGame(self, room_jid, players=[], profile_key='@NONE@'):
        """Create a new game
        @param room_jid: jid of the room
        @param players: list of players nick (nick must exist in the room)
        @param profile_key: %(doc_profile_key)s"""
        debug(_("Creating %s game") % self.name)
        room = JID(room_jid).userhost()
        profile = self.host.memory.getProfileName(profile_key)
        if not profile:
            error(_("profile %s is unknown") % profile_key)
            return
        room_nick = self.host.plugins["XEP-0045"].getRoomNick(room, profile)
        if not room_nick:
            error('Internal error')
            return
        referee = room + '/' + room_nick
        if room in self.games:
            warning(_("%s game already started in room %s") % (self.name, room))
            return
        self.games[room] = {'referee': referee}
        self.games[room].update(self.options)
        if self.collectiveGame is True:
            self.send(JID(room), self.createStartElement(), profile=profile)
            return
        # non collaborative game = individual data and messages
        status = {}
        players_data = {}
        for player in players:
            # The dict must be COPIED otherwise it is shared between all users
            players_data[player] = self.player_init_data.copy()
            status[player] = "init"
            # each player send a message to all the others
            self.send(JID(room + '/' + player), self.createStartElement(players), profile=profile)
        # specific data to each player
        self.games[room].update({'players': players, 'status': status, 'players_data': players_data})

    def createCollectiveGame(self, room_jid, profile_key='@NONE@'):
        return self.createGame(self, room_jid, players=[], profile_key=profile_key)

    def playerReady(self, player, referee, profile_key='@NONE@'):
        """Must be called when player is ready to start a new game"""
        profile = self.host.memory.getProfileName(profile_key)
        if not profile:
            error(_("profile %s is unknown") % profile_key)
            return
        debug('new player ready: %s' % profile)
        self.send(JID(referee), 'player_ready', {'player': player}, profile=profile)

    def newRound(self, room_jid, data, profile):
        """Launch a new round (reinit the user data)"""
        debug(_('new round for %s game') % self.name)
        game_data = self.games[room_jid.userhost()]
        players = game_data['players']
        players_data = game_data['players_data']
        game_data['stage'] = "init"

        common_data, msg_elts = data if data is not None else (None, None)

        if isinstance(msg_elts, dict):
            for player in players:
                to_jid = JID(room_jid.userhost() + "/" + player)  # FIXME: gof:
                elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None
                self.send(to_jid, elem, profile=profile)
        elif isinstance(msg_elts, domish.Element):
            self.send(room_jid, msg_elts, profile=profile)
        if common_data is not None:
            for player in players:
                players_data[player].update(common_data)

    def createGameElt(self, to_jid, type_="normal"):
        """Create a generic domish Element for the game"""
        type_ = "normal" if to_jid.resource else "groupchat"
        elt = domish.Element((None, 'message'))
        elt["to"] = to_jid.full()
        elt["type"] = type_
        elt.addElement(self.ns_tag)
        return elt

    def createStartElement(self, players=None):
        """Create a game "started" domish Element"""
        started_elt = domish.Element((None, 'started'))
        if players is None:
            return started_elt
        idx = 0
        for player in players:
            player_elt = domish.Element((None, 'player'))
            player_elt.addContent(player)
            player_elt['index'] = str(idx)
            idx += 1
            started_elt.addChild(player_elt)
        return started_elt

    def send(self, to_jid, elem=None, attrs=None, content=None, profile=None):
        """
        @param to_jid: recipient JID
        @param elem: domish.Element, unicode or a couple:
        - domish.Element to be directly added as a child to the message
        - unicode name or couple (uri, name) to create a new domish.Element
          and add it as a child to the message (see domish.Element.addElement)
        @param attrs: dictionary of attributes for the new child
        @param content: unicode that is appended to the child content
        @param profile: the profile from which the message is sent
        """
        if profile is None:
            error(_("Message can not be sent without a sender profile"))
            return
        msg = self.createGameElt(to_jid)
        if elem is not None:
            if isinstance(elem, domish.Element):
                msg.firstChildElement().addChild(elem)
            else:
                elem = msg.firstChildElement().addElement(elem)
            if attrs is not None:
                elem.attributes.update(attrs)
            if content is not None:
                elem.addContent(content)
        self.host.profiles[profile].xmlstream.send(msg)