Esempio n. 1
0
async def test_cancel_twice(ladder_service: LadderService):
    p1 = mock.create_autospec(Player('Dostya', id=1))
    p1.ladder_rating = (1500, 500)
    p1.numGames = 0

    p2 = mock.create_autospec(Player('Brackman', id=1))
    p2.ladder_rating = (2000, 50)
    p2.numGames = 0

    search = Search([p1])
    search2 = Search([p2])

    ladder_service.start_search(p1, search, 'ladder1v1')
    ladder_service.start_search(p2, search2, 'ladder1v1')

    searches = ladder_service._cancel_existing_searches(p1)
    assert search.is_cancelled
    assert searches == [search]
    assert not search2.is_cancelled

    searches = ladder_service._cancel_existing_searches(p1)
    assert searches == []

    searches = ladder_service._cancel_existing_searches(p2)
    assert search2.is_cancelled
    assert searches == [search2]
Esempio n. 2
0
def test_serialize():
    p = Player(player_id=42,
               login="******",
               ratings={
                   RatingType.GLOBAL: (1234, 68),
                   RatingType.LADDER_1V1: (1500, 230),
               },
               clan="TOAST",
               game_count={RatingType.GLOBAL: 542})
    assert p.to_dict() == {
        "id": 42,
        "login": "******",
        "clan": "TOAST",
        "ratings": {
            "global": {
                "rating": (1234, 68),
                "number_of_games": 542
            },
            "ladder_1v1": {
                "rating": (1500, 230),
                "number_of_games": 0
            }
        },
        "global_rating": (1234, 68),
        "ladder_rating": (1500, 230),
        "number_of_games": 542,
    }
Esempio n. 3
0
def test_unlink_weakref():
    p = Player(login='******')
    mock_game = mock.Mock()
    p.game = mock_game
    assert p.game == mock_game
    del p.game
    assert p.game is None
Esempio n. 4
0
def test_unlink_weakref():
    p = Player(login='******')
    mock_game = mock.Mock()
    p.game = mock_game
    assert p.game == mock_game
    del p.game
    assert p.game is None
Esempio n. 5
0
def test_ratings():
    p = Player('Schroedinger')
    p.global_rating = (1500, 20)
    assert p.global_rating == (1500, 20)
    p.global_rating = Rating(1700, 20)
    assert p.global_rating == (1700, 20)
    p.ladder_rating = (1200, 20)
    assert p.ladder_rating == (1200, 20)
    p.ladder_rating = Rating(1200, 20)
    assert p.ladder_rating == (1200, 20)
Esempio n. 6
0
def test_faction():
    """
    Yes, this test was motivated by a bug
    :return:
    """
    p = Player('Schroedinger2')
    p.faction = 'aeon'
    assert p.faction == Faction.aeon
    p.faction = Faction.aeon
    assert p.faction == Faction.aeon
Esempio n. 7
0
def test_faction():
    """
    Yes, this test was motivated by a bug
    :return:
    """
    p = Player('Schroedinger2')
    p.faction = 'aeon'
    assert p.faction == Faction.aeon
    p.faction = Faction.aeon
    assert p.faction == Faction.aeon
Esempio n. 8
0
def game_5p(game):
    game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500)),
        Player(id=3, login='******', global_rating=(1500, 500)),
        Player(id=4, login='******', global_rating=(1500, 500)),
        Player(id=5, login='******', global_rating=(1500, 500)),
    ]
    add_connected_players(game, players)
    return game
Esempio n. 9
0
async def test_start_game(ladder_service: LadderService,
                          game_service: GameService):
    p1 = mock.create_autospec(Player('Dostya', id=1))
    p2 = mock.create_autospec(Player('Rhiza', id=2))
    game_service.ladder_maps = [(1, 'scmp_007', 'maps/scmp_007.zip')]

    with mock.patch('asyncio.sleep', CoroMock()):
        await ladder_service.start_game(p1, p2)

    assert p1.lobby_connection.launch_game.called
    assert p2.lobby_connection.launch_game.called
Esempio n. 10
0
async def test_clear_slot(game: Game, game_connection: GameConnection):
    game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500))
    ]
    add_connected_players(game, players)

    game.clear_slot(0)

    assert game.get_player_option(1, 'StartSpot') == -1
    assert game.get_player_option(1, 'Team') == -1
    assert game.get_player_option(1, 'Army') == -1
    assert game.get_player_option(2, 'StartSpot') == 1
Esempio n. 11
0
def test_serialize():
    p = Player(id=42,
               login='******',
               global_rating=(1234, 68),
               ladder_rating=(1500, 230),
               clan='TOAST',
               numGames=542)
    assert p.to_dict() == {
                    "id": 42,
                    "login": '******',
                    "global_rating": (1234, 68),
                    "ladder_rating": (1500, 230),
                    "number_of_games": 542,
                    "clan": 'TOAST'
    }
Esempio n. 12
0
def test_serialize():
    p = Player(id=42,
               login='******',
               global_rating=(1234, 68),
               ladder_rating=(1500, 230),
               clan='TOAST',
               numGames=542)
    assert p.to_dict() == {
        "id": 42,
        "login": '******',
        "global_rating": (1234, 68),
        "ladder_rating": (1500, 230),
        "number_of_games": 542,
        "clan": 'TOAST'
    }
Esempio n. 13
0
def test_inform_player(ladder_service: LadderService):
    p1 = mock.create_autospec(Player('Dostya', id=1))
    p1.ladder_rating = (1500, 500)

    ladder_service.inform_player(p1)

    assert p1.lobby_connection.sendJSON.called
Esempio n. 14
0
def playerMock(lobbythread, id):
    player_mock = mock.create_autospec(spec=Player(''))
    player_mock.login = "******" % id
    player_mock.id = id
    player_mock.game_port = 4242
    player_mock.lobby_connection = lobbythread
    return player_mock
Esempio n. 15
0
def test_serialize():
    p = Player(player_id=42,
               login='******',
               ratings={
                   RatingType.GLOBAL: (1234, 68),
                   RatingType.LADDER_1V1: (1500, 230),
               },
               clan='TOAST',
               game_count={RatingType.GLOBAL: 542})
    assert p.to_dict() == {
        "id": 42,
        "login": '******',
        "global_rating": (1234, 68),
        "ladder_rating": (1500, 230),
        "number_of_games": 542,
        "clan": 'TOAST'
    }
Esempio n. 16
0
async def test_queue_many(mocker, player_service, matchmaker_queue):
    p1, p2, p3 = Player('Dostya', id=1, ladder_rating=(2200, 150), numGames=(config.NEWBIE_MIN_GAMES+1)), \
                 Player('Brackman', id=2, ladder_rating=(1500, 150), numGames=(config.NEWBIE_MIN_GAMES+1)), \
                 Player('Zoidberg', id=3, ladder_rating=(1500, 125), numGames=(config.NEWBIE_MIN_GAMES+1))

    player_service.players = {p1.id: p1, p2.id: p2, p3.id: p3}
    s1 = Search([p1])
    s2 = Search([p2])
    s3 = Search([p3])
    matchmaker_queue.push(s1)
    matchmaker_queue.push(s2)
    matchmaker_queue.push(s3)

    matchmaker_queue.find_matches()

    assert not s1.is_matched
    assert s2.is_matched
    assert s3.is_matched
Esempio n. 17
0
def test_queue_race(mocker, player_service, matchmaker_queue):
    p1, p2, p3 = Player('Dostya', id=1, ladder_rating=(2300, 150)), \
                 Player('Brackman', id=2, ladder_rating=(2200, 150)), \
                 Player('Zoidberg', id=3, ladder_rating=(2300, 125))

    player_service.players = {p1.id: p1, p2.id: p2, p3.id: p3}

    matchmaker_queue.game_service.ladder_service.start_game = CoroMock()

    try:
        yield from asyncio.gather(
            asyncio.wait_for(matchmaker_queue.search(p1), 0.1),
            asyncio.wait_for(matchmaker_queue.search(p2), 0.1),
            asyncio.wait_for(matchmaker_queue.search(p3), 0.1))
    except (TimeoutError, CancelledError):
        pass

    assert len(matchmaker_queue) == 0
Esempio n. 18
0
async def test_report_army_stats_sends_stats_for_defeated_player(game: Game):
    game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500))
    ]
    add_connected_players(game, players)

    await game.launch()
    await game.add_result(0, 1, 'defeat', -1)

    with open("tests/data/game_stats_simple_win.json", "r") as stats_file:
        stats = stats_file.read()

    await game.report_army_stats(stats)

    game._game_stats_service.process_game_stats.assert_called_once_with(
        players[1], game, stats)
Esempio n. 19
0
async def test_rate_game_late_abort_no_enforce(game_service,
                                               game_stats_service,
                                               custom_game):
    custom_game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500)),
    ]
    add_connected_players(custom_game, players)
    custom_game.set_player_option(1, 'Team', 2)
    custom_game.set_player_option(2, 'Team', 3)
    await custom_game.launch()
    await custom_game.add_result(0, 1, 'VICTORY', 5)

    custom_game.launched_at = time.time() - 600  # seconds

    await custom_game.on_game_end()
    assert custom_game.validity == ValidityState.VALID
Esempio n. 20
0
def test_ratings():
    p = Player('Schroedinger')
    p.ratings[RatingType.GLOBAL] = (1500, 20)
    assert p.ratings[RatingType.GLOBAL] == (1500, 20)
    p.ratings[RatingType.GLOBAL] = Rating(1700, 20)
    assert p.ratings[RatingType.GLOBAL] == (1700, 20)
    p.ratings[RatingType.LADDER_1V1] = (1200, 20)
    assert p.ratings[RatingType.LADDER_1V1] == (1200, 20)
    p.ratings[RatingType.LADDER_1V1] = Rating(1200, 20)
    assert p.ratings[RatingType.LADDER_1V1] == (1200, 20)
Esempio n. 21
0
async def test_start_game_called_on_match(ladder_service: LadderService):
    p1 = mock.create_autospec(Player('Dostya', id=1))
    p1.ladder_rating = (2300, 64)
    p1.numGames = 0

    p2 = mock.create_autospec(Player('QAI', id=4))
    p2.ladder_rating = (2350, 125)
    p2.numGames = 0

    ladder_service.start_game = CoroMock()
    ladder_service.inform_player = mock.Mock()

    ladder_service.start_search(p1, Search([p1]), 'ladder1v1')
    ladder_service.start_search(p2, Search([p2]), 'ladder1v1')

    await asyncio.sleep(1)

    ladder_service.inform_player.assert_called()
    ladder_service.start_game.assert_called_once()
Esempio n. 22
0
async def test_persist_results_called_with_two_players(game):
    await game.clear_data()

    game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500))
    ]
    add_connected_players(game, players)
    await game.launch()
    assert len(game.players) == 2
    await game.add_result(0, 1, 'VICTORY', 5)
    await game.on_game_end()

    assert game.get_army_result(1) == 5
    assert len(game.players) == 2

    await game.load_results()
    assert game.get_army_result(1) == 5
Esempio n. 23
0
async def test_start_game_timeout(ladder_service: LadderService,
                                  game_service: GameService):
    p1 = mock.create_autospec(Player('Dostya', id=1))
    p2 = mock.create_autospec(Player('Rhiza', id=2))

    p1.id = 1
    p2.id = 2
    game_service.ladder_maps = [(1, 'scmp_007', 'maps/scmp_007.zip')]

    with mock.patch('server.games.game.Game.sleep', CoroMock()):
        await ladder_service.start_game(p1, p2)

    p1.lobby_connection.send.assert_called_once_with(
        {"command": "game_launch_timeout"})
    p2.lobby_connection.send.assert_called_once_with(
        {"command": "game_launch_timeout"})
    assert p1.lobby_connection.launch_game.called
    # TODO: Once client supports `game_launch_timeout` change this to `assert not ...`
    assert p2.lobby_connection.launch_game.called
Esempio n. 24
0
def make_player(state=PlayerState.IDLE,
                global_rating=None,
                ladder_rating=None,
                numGames=0,
                ladder_games=0,
                **kwargs):
    ratings = {
        k: v
        for k, v in {
            RatingType.GLOBAL: global_rating,
            RatingType.LADDER_1V1: ladder_rating,
        }.items() if v is not None
    }

    games = {RatingType.GLOBAL: numGames, RatingType.LADDER_1V1: ladder_games}

    p = Player(ratings=ratings, game_count=games, **kwargs)
    p.state = state
    return p
Esempio n. 25
0
 def make(login='', id=0, port=6112, state=PlayerState.HOSTING, ip='127.0.0.1', global_rating=Rating(1500, 250), ladder_rating=Rating(1500, 250)):
     p = mock.create_autospec(spec=Player(login))
     p.global_rating = global_rating
     p.ladder_rating = ladder_rating
     p.ip = ip
     p.game_port = port
     p.state = state
     p.id = id
     p.login = login
     p.address_and_port = "{}:{}".format(ip, port)
     return p
Esempio n. 26
0
async def test_queue_race(mocker, player_service, matchmaker_queue):
    p1, p2, p3 = Player('Dostya', id=1, ladder_rating=(2300, 150), numGames=(config.NEWBIE_MIN_GAMES+1)), \
                 Player('Brackman', id=2, ladder_rating=(2200, 150), numGames=(config.NEWBIE_MIN_GAMES+1)), \
                 Player('Zoidberg', id=3, ladder_rating=(2300, 125), numGames=(config.NEWBIE_MIN_GAMES+1))

    player_service.players = {p1.id: p1, p2.id: p2, p3.id: p3}

    async def find_matches():
        await asyncio.sleep(0.01)
        matchmaker_queue.find_matches()

    try:
        await asyncio.gather(
            asyncio.wait_for(matchmaker_queue.search(Search([p1])), 0.1),
            asyncio.wait_for(matchmaker_queue.search(Search([p2])), 0.1),
            asyncio.wait_for(matchmaker_queue.search(Search([p3])), 0.1),
            asyncio.ensure_future(find_matches()))
    except (TimeoutError, CancelledError):
        pass

    assert len(matchmaker_queue) == 0
Esempio n. 27
0
def test_ratings():
    p = Player("Schroedinger")
    p.ratings[RatingType.GLOBAL] = (1500, 20)
    assert p.ratings[RatingType.GLOBAL] == (1500, 20)
    p.ratings[RatingType.GLOBAL] = Rating(1700, 20)
    assert p.ratings[RatingType.GLOBAL] == (1700, 20)
    assert p.ratings["tmm_2v2"] == (1700, 170)
    p.ratings[RatingType.LADDER_1V1] = (1200, 20)
    assert p.ratings[RatingType.LADDER_1V1] == (1200, 20)
    p.ratings[RatingType.LADDER_1V1] = Rating(1200, 20)
    assert p.ratings[RatingType.LADDER_1V1] == (1200, 20)
    assert p.ratings["Something completely different"] == (1500, 500)
Esempio n. 28
0
def test_weak_references():
    p = Player(login='******')
    weak_properties = ['lobby_connection', 'game']
    referent = mock.Mock()
    for prop in weak_properties:
        setattr(p, prop, referent)

    del referent
    gc.collect()

    for prop in weak_properties:
        assert getattr(p, prop) is None
Esempio n. 29
0
async def test_on_game_end_calls_rate_game_with_two_players(game):
    await game.clear_data()
    game.rate_game = CoroMock()

    game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500))
    ]
    add_connected_players(game, players)
    await game.launch()

    assert len(game.players) == 2
    await game.add_result(0, 1, 'victory', 10)
    await game.add_result(1, 2, 'defeat', -10)

    await game.on_game_end()
    assert game.state == GameState.ENDED
    game.rate_game.assert_any_call()

    assert game.validity is ValidityState.VALID
Esempio n. 30
0
def test_weak_references():
    p = Player(login="******")
    weak_properties = ["lobby_connection", "game", "game_connection"]
    referent = mock.Mock()
    for prop in weak_properties:
        setattr(p, prop, referent)

    del referent
    gc.collect()

    for prop in weak_properties:
        assert getattr(p, prop) is None
Esempio n. 31
0
def matchmaker_players():
    return Player('Dostya',   id=1, ladder_rating=(2300, 64), numGames=(config.NEWBIE_MIN_GAMES+1)), \
           Player('Brackman', id=2, ladder_rating=(1200, 72), numGames=(config.NEWBIE_MIN_GAMES+1)), \
           Player('Zoidberg', id=3, ladder_rating=(1300, 175), numGames=(config.NEWBIE_MIN_GAMES+1)), \
           Player('QAI',      id=4, ladder_rating=(2350, 125), numGames=(config.NEWBIE_MIN_GAMES+1)), \
           Player('Rhiza',    id=5, ladder_rating=(1200, 175), numGames=(config.NEWBIE_MIN_GAMES+1)), \
           Player('Newbie',   id=6, ladder_rating=(1200, 175), numGames=(config.NEWBIE_MIN_GAMES-1))
Esempio n. 32
0
async def test_game_ends_in_mutually_agreed_draw(game: Game):
    await game.clear_data()
    game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500))
    ]
    add_connected_players(game, players)
    await game.launch()

    for player in players:
        game.set_player_option(player.id, 'Team', 1)
        game.set_player_option(player.id, 'Army', player.id - 1)

    game.state = GameState.LIVE
    game.launched_at = time.time() - 60 * 60

    await game.add_result(players[0], 0, 'mutual_draw', 0)
    await game.add_result(players[1], 1, 'mutual_draw', 0)
    await game.on_game_end()

    assert game.validity is ValidityState.MUTUAL_DRAW
Esempio n. 33
0
async def test_persist_results_not_called_with_one_player(game):
    await game.clear_data()
    game.persist_results = CoroMock()

    game.state = GameState.LOBBY
    players = [Player(id=1, login='******', global_rating=(1500, 500))]
    add_connected_players(game, players)
    await game.launch()
    assert len(game.players) == 1
    await game.add_result(0, 1, 'VICTORY', 5)
    await game.on_game_end()

    game.persist_results.assert_not_called()
Esempio n. 34
0
async def test_players_exclude_observers(game: Game):
    game.state = GameState.LOBBY
    players = [
        Player(id=1, login='******', global_rating=(1500, 500)),
        Player(id=2, login='******', global_rating=(1500, 500)),
    ]
    add_connected_players(game, players)

    obs = Player(id=3, login='******', global_rating=(1500, 500))

    game.game_service.player_service[obs.id] = obs
    gc = mock_game_connection(state=GameConnectionState.CONNECTED_TO_HOST,
                              player=obs)
    game.set_player_option(obs.id, 'Army', -1)
    game.set_player_option(obs.id, 'StartSpot', -1)
    game.set_player_option(obs.id, 'Team', 0)
    game.set_player_option(obs.id, 'Faction', 0)
    game.set_player_option(obs.id, 'Color', 0)
    game.add_game_connection(gc)
    await game.launch()

    assert game.players == frozenset(players)
Esempio n. 35
0
    async def command_hello(self, message):
        login = message['login'].strip()
        password = message['password']

        async with db.db_pool.get() as conn:
            cursor = await conn.cursor()
            player_id, login, steamid = await self.check_user_login(cursor, login, password)
            server.stats.incr('user.logins')
            server.stats.gauge('users.online', len(self.player_service))

            await cursor.execute("UPDATE login SET ip = %(ip)s, user_agent = %(user_agent)s WHERE id = %(player_id)s", {
                                     "ip": self.peer_address.host,
                                     "user_agent": self.user_agent,
                                     "player_id": player_id
                                 })

            if not self.player_service.is_uniqueid_exempt(player_id) and steamid is None:
                conforms_policy = await self.check_policy_conformity(player_id, message['unique_id'], self.session)
                if not conforms_policy:
                    return


            # Update the user's IRC registration (why the f**k is this here?!)
            m = hashlib.md5()
            m.update(password.encode())
            passwordmd5 = m.hexdigest()
            m = hashlib.md5()
            # Since the password is hashed on the client, what we get at this point is really
            # md5(md5(sha256(password))). This is entirely insane.
            m.update(passwordmd5.encode())
            irc_pass = "******" + str(m.hexdigest())

            try:
                await cursor.execute("UPDATE anope.anope_db_NickCore SET pass = %s WHERE display = %s", (irc_pass, login))
            except (pymysql.OperationalError, pymysql.ProgrammingError):
                self._logger.error("Failure updating NickServ password for %s", login)

        permission_group = self.player_service.get_permission_group(player_id)
        self.player = Player(login=str(login),
                             session=self.session,
                             ip=self.peer_address.host,
                             port=None,
                             id=player_id,
                             permissionGroup=permission_group,
                             lobby_connection=self)
        self.connectivity = Connectivity(self, self.peer_address.host, self.player)

        if self.player.id in self.player_service and self.player_service[self.player.id].lobby_connection:
            old_conn = self.player_service[self.player.id].lobby_connection
            old_conn.send_warning("You have been signed out because you signed in elsewhere.", fatal=True)

        await self.player_service.fetch_player_data(self.player)

        self.player_service[self.player.id] = self.player
        self._authenticated = True

        # Country
        # -------
        try:
            self.player.country = str(gi.country(self.peer_address.host).country.iso_code)
        except (geoip2.errors.AddressNotFoundError,ValueError):
            self.player.country = ''

        ## AVATARS
        ## -------------------
        async with db.db_pool.get() as conn:
            cursor = await conn.cursor()
            await cursor.execute(
                "SELECT url, tooltip FROM `avatars` "
                "LEFT JOIN `avatars_list` ON `idAvatar` = `avatars_list`.`id` "
                "WHERE `idUser` = %s AND `selected` = 1", (self.player.id, ))
            avatar = await cursor.fetchone()
            if avatar:
                url, tooltip = avatar
                self.player.avatar = {"url": url, "tooltip": tooltip}

        # Send the player their own player info.
        self.sendJSON({
            "command": "welcome",
            "me": self.player.to_dict(),

            # For backwards compatibility for old clients. For now.
            "id": self.player.id,
            "login": login
        })

        # Tell player about everybody online. This must happen after "welcome".
        self.sendJSON(
            {
                "command": "player_info",
                "players": [player.to_dict() for player in self.player_service]
            }
        )

        # Tell everyone else online about us. This must happen after all the player_info messages.
        # This ensures that no other client will perform an operation that interacts with the
        # incoming user, allowing the client to make useful assumptions: it can be certain it has
        # initialised its local player service before it is going to get messages that want to
        # query it.
        self.player_service.mark_dirty(self.player)

        friends = []
        foes = []
        async with db.db_pool.get() as conn:
            cursor = await conn.cursor()
            await cursor.execute("SELECT `subject_id`, `status` "
                                 "FROM friends_and_foes WHERE user_id = %s", (self.player.id,))

            for target_id, status in await cursor.fetchall():
                if status == "FRIEND":
                    friends.append(target_id)
                else:
                    foes.append(target_id)

        self.player.friends = set(friends)
        self.player.foes = set(foes)

        channels = []
        if self.player.mod:
            channels.append("#moderators")

        if self.player.clan is not None:
            channels.append("#%s_clan" % self.player.clan)

        jsonToSend = {"command": "social", "autojoin": channels, "channels": channels, "friends": friends, "foes": foes, "power": permission_group}
        self.sendJSON(jsonToSend)

        self.send_mod_list()
        self.send_game_list()
        self.send_tutorial_section()
Esempio n. 36
0
class LobbyConnection:
    @timed()
    def __init__(self, loop, context=None, games: GameService=None, players: PlayerService=None, db=None):
        super(LobbyConnection, self).__init__()
        self.loop = loop
        self.db = db
        self.game_service = games
        self.player_service = players  # type: PlayerService
        self.context = context
        self.ladderPotentialPlayers = []
        self.warned = False
        self._authenticated = False
        self.player = None  # type: Player
        self.game_connection = None  # type: GameConnection
        self.connectivity = None  # type: Connectivity
        self.leagueAvatar = None
        self.peer_address = None  # type: Optional[Address]
        self.session = int(random.randrange(0, 4294967295))
        self.protocol = None
        self._logger.debug("LobbyConnection initialized")
        self.search = None
        self.user_agent = None

    @property
    def authenticated(self):
        return self._authenticated

    @asyncio.coroutine
    def on_connection_made(self, protocol: QDataStreamProtocol, peername: Address):
        self.protocol = protocol
        self.peer_address = peername
        server.stats.incr("server.connections")

    def abort(self, logspam=""):
        if self.player:
            self._logger.warning("Client %s dropped. %s" % (self.player.login, logspam))
        else:
            self._logger.warning("Aborting %s. %s" % (self.peer_address.host, logspam))
        if self.game_connection:
            self.game_connection.abort()
        self._authenticated = False
        self.protocol.writer.close()

    def ensure_authenticated(self, cmd):
        if not self._authenticated:
            if cmd not in ['hello', 'ask_session', 'create_account', 'ping', 'pong']:
                self.abort("Message invalid for unauthenticated connection: %s" % cmd)
                return False
        return True

    async def on_message_received(self, message):
        """
        Dispatches incoming messages
        """
        try:
            cmd = message['command']
            if not self.ensure_authenticated(cmd):
                return
            target = message.get('target')
            if target == 'game':
                if not self.game_connection:
                    return
                await self.game_connection.handle_action(cmd, message.get('args', []))
                return
            elif target == 'connectivity':
                if not self.connectivity:
                    return
                await self.connectivity.on_message_received(message)
                return
            handler = getattr(self, 'command_{}'.format(cmd))
            if asyncio.iscoroutinefunction(handler):
                await handler(message)
            else:
                handler(message)
        except AuthenticationError as ex:
            self.protocol.send_message(
                {'command': 'authentication_failed',
                 'text': ex.message}
            )
        except ClientError as ex:
            self.protocol.send_message(
                {'command': 'notice',
                 'style': 'error',
                 'text': ex.message}
            )
            if not ex.recoverable:
                self.abort(ex.message)
        except (KeyError, ValueError) as ex:
            self._logger.exception(ex)
            self.abort("Garbage command: {}".format(message))
        except Exception as ex:
            self.protocol.send_message({'command': 'invalid'})
            self._logger.exception(ex)
            self.abort("Error processing command")

    def command_ping(self, msg):
        self.protocol.send_raw(self.protocol.pack_message('PONG'))

    def command_pong(self, msg):
        pass

    @staticmethod
    def generate_expiring_request(lifetime, plaintext):
        """
        Generate the parameters needed for an expiring email request with the given payload.
        Payload should be comma-delimited, and the consumer should expect to find and verify
        a timestamp and nonce appended to the given plaintext.
        """

        # Add nonce
        rng = Random.new()
        nonce = ''.join(choice(string.ascii_uppercase + string.digits) for _ in range(256))

        expiry = str(time.time() + lifetime)

        plaintext = (plaintext + "," + expiry + "," + nonce).encode('utf-8')

        # Pad the plaintext to the next full block with commas, because I can't be arsed to
        # write an actually clever parser.
        bs = Blowfish.block_size
        paddinglen = bs - (len(plaintext) % bs)
        plaintext += b',' * paddinglen

        # Generate random IV of size one block.
        iv = rng.read(bs)
        cipher = Blowfish.new(VERIFICATION_SECRET_KEY, Blowfish.MODE_CBC, iv)
        ciphertext = cipher.encrypt(plaintext)

        # Generate the verification hash.
        verification = hashlib.sha256()
        verification.update(plaintext + VERIFICATION_HASH_SECRET.encode('utf-8'))
        verify_hex = verification.hexdigest()

        return base64.urlsafe_b64encode(iv), base64.urlsafe_b64encode(ciphertext), verify_hex

    @asyncio.coroutine
    def command_create_account(self, message):
        raise ClientError("FAF no longer supports direct registration. Please use the website to register.", recoverable=True)

    @timed()
    def send_tutorial_section(self):
        reply = []

        with (yield from db.db_pool) as conn:
            cursor = yield from conn.cursor()

            # Can probably replace two queries with one here if we're smart enough.
            yield from cursor.execute("SELECT `section`,`description` FROM `tutorial_sections`")

            for i in range(0, cursor.rowcount):
                section, description = yield from cursor.fetchone()
                reply.append( {"command": "tutorials_info", "section": section, "description": description})

            yield from cursor.execute("SELECT tutorial_sections.`section`, `name`, `url`, `tutorials`.`description`, `map` FROM `tutorials` LEFT JOIN  tutorial_sections ON tutorial_sections.id = tutorials.section ORDER BY `tutorials`.`section`, name")

            for i in range(0, cursor.rowcount):
                section, tutorial_name, url, description, map_name = yield from cursor.fetchone()
                reply.append({"command": "tutorials_info", "tutorial": tutorial_name, "url": url,
                              "tutorial_section": section, "description": description,
                              "mapname": map_name})

        self.protocol.send_messages(reply)

    async def send_coop_maps(self):
        async with db.db_pool.get() as conn:
            cursor = await conn.cursor()

            await cursor.execute("SELECT name, description, filename, type, id FROM `coop_map`")

            maps = []
            for i in range(0, cursor.rowcount):
                name, description, filename, type, id = await cursor.fetchone()
                jsonToSend = {"command": "coop_info", "name": name, "description": description,
                              "filename": filename, "featured_mod": "coop"}
                if type == 0:
                    jsonToSend["type"] = "FA Campaign"
                elif type == 1:
                    jsonToSend["type"] = "Aeon Vanilla Campaign"
                elif type == 2:
                    jsonToSend["type"] = "Cybran Vanilla Campaign"
                elif type == 3:
                    jsonToSend["type"] = "UEF Vanilla Campaign"
                elif type == 4:
                    jsonToSend["type"] = "Custom Missions"
                else:
                    # Don't sent corrupt data to the client...
                    self._logger.error("Unknown coop type!")
                    return
                jsonToSend["uid"] = id
                maps.append(jsonToSend)

        self.protocol.send_messages(maps)

    @timed
    def send_mod_list(self):
        self.protocol.send_messages(self.game_service.all_game_modes())

    @timed()
    def send_game_list(self):
        self.sendJSON({
            'command': 'game_info',
            'games': [game.to_dict() for game in self.game_service.open_games]
        })

    @asyncio.coroutine
    def command_social_remove(self, message):
        if "friend" in message:
            target_id = message['friend']
        elif "foe" in message:
            target_id = message['foe']
        else:
            self.abort("No-op social_remove.")
            return

        with (yield from db.db_pool) as conn:
            cursor = yield from conn.cursor()

            yield from cursor.execute("DELETE FROM friends_and_foes WHERE user_id = %s AND subject_id = %s",
                                      (self.player.id, target_id))

    @timed()
    @asyncio.coroutine
    def command_social_add(self, message):
        if "friend" in message:
            status = "FRIEND"
            target_id = message['friend']
        elif "foe" in message:
            status = "FOE"
            target_id = message['foe']
        else:
            return

        with (yield from db.db_pool) as conn:
            cursor = yield from conn.cursor()

            yield from cursor.execute("INSERT INTO friends_and_foes(user_id, subject_id, `status`) VALUES(%s, %s, %s)",
                                      (self.player.id, target_id, status))

    def kick(self, message=None):
        self.sendJSON(dict(command="notice", style="kick"))
        if message:
            self.sendJSON(dict(command="notice", style="info",
                                                  text=message))
        self.abort()

    def send_updated_achievements(self, updated_achievements):
        self.sendJSON(dict(command="updated_achievements", updated_achievements=updated_achievements))

    @asyncio.coroutine
    def command_admin(self, message):
        action = message['action']

        if self.player.admin:
            if action == "closeFA":
                player = self.player_service[message['user_id']]
                if player:
                    self._logger.warn('Administrative action: %s closed game for %s', self.player, player)
                    player.lobby_connection.sendJSON(dict(command="notice", style="kill"))
                    player.lobby_connection.sendJSON(dict(command="notice", style="info",
                                       text=("Your game was closed by an administrator ({admin_name}). "
                                             "Please refer to our rules for the lobby/game here {rule_link}."
                                       .format(admin_name=self.player.login,
                                               rule_link=config.RULE_LINK))))

            elif action == "closelobby":
                player = self.player_service[message['user_id']]
                ban_fail = None
                if player:
                    if 'ban' in message:
                        reason = message['ban'].get('reason', 'Unspecified')
                        duration = int(message['ban'].get('duration', 1))
                        period = message['ban'].get('period', 'DAY')
                        self._logger.warn('Administrative action: %s closed client for %s with %s ban (Reason: %s)', self.player, player, duration, reason)
                        with (yield from db.db_pool) as conn:
                            try:
                                cursor = yield from conn.cursor()

                                yield from cursor.execute("SELECT reason from lobby_ban WHERE idUser=%s AND expires_at > NOW()", (message['user_id']))

                                if cursor.rowcount > 0:
                                    ban_fail = yield from cursor.fetchone()
                                else:
                                    # XXX Interpolating the period into this is terrible and insecure - but the data comes from trusted users (admins) only
                                    yield from cursor.execute("INSERT INTO ban (player_id, author_id, reason, expires_at, level) VALUES (%s, %s, %s, DATE_ADD(NOW(), INTERVAL %s {}), 'GLOBAL')".format(period), (player.id, self.player.id, reason, duration))
                            except pymysql.MySQLError as e:
                                raise ClientError('Your ban attempt upset the database: {}'.format(e))
                    else:
                        self._logger.warn('Administrative action: %s closed client for %s', self.player, player)
                    player.lobby_connection.kick(
                        message=("You were kicked from FAF by an administrator ({admin_name}). "
                         "Please refer to our rules for the lobby/game here {rule_link}."
                          .format(admin_name=self.player.login,
                                  rule_link=config.RULE_LINK)))
                    if ban_fail:
                        raise ClientError("Kicked the player, but he was already banned!")

            elif action == "requestavatars":
                with (yield from db.db_pool) as conn:
                    cursor = yield from conn.cursor()
                    yield from cursor.execute("SELECT url, tooltip FROM `avatars_list`")

                    avatars = yield from cursor.fetchall()
                    data = {"command": "admin", "avatarlist": []}
                    for url, tooltip in avatars:
                        data['avatarlist'].append({"url": url, "tooltip": tooltip})

                    self.sendJSON(data)

            elif action == "remove_avatar":
                idavatar = message["idavatar"]
                iduser = message["iduser"]
                with (yield from db.db_pool) as conn:
                    cursor = yield from conn.cursor()
                    yield from cursor.execute("DELETE FROM `avatars` "
                                              "WHERE `idUser` = %s "
                                              "AND `idAvatar` = %s", (iduser, idavatar))

            elif action == "add_avatar":
                who = message['user']
                avatar = message['avatar']

                with (yield from db.db_pool) as conn:
                    cursor = yield from conn.cursor()
                    if avatar is None:
                        yield from cursor.execute(
                            "DELETE FROM `avatars` "
                            "WHERE `idUser` = "
                            "(SELECT `id` FROM `login` WHERE `login`.`login` = %s)", (who, ))
                    else:
                        yield from cursor.execute(
                            "INSERT INTO `avatars`(`idUser`, `idAvatar`) "
                            "VALUES ((SELECT id FROM login WHERE login.login = %s),"
                            "(SELECT id FROM avatars_list WHERE avatars_list.url = %s)) "
                            "ON DUPLICATE KEY UPDATE `idAvatar` = (SELECT id FROM avatars_list WHERE avatars_list.url = %s)",
                            (who, avatar, avatar))

            elif action == "broadcast":
                for player in self.player_service:
                    try:
                        if player.lobby_connection:
                            player.lobby_connection.send_warning(message.get('message'))
                    except Exception as ex:
                        self._logger.debug("Could not send broadcast message to %s: %s".format(player, ex))

        elif self.player.mod:
            if action == "join_channel":
                user_ids = message['user_ids']
                channel = message['channel']

                for user_id in user_ids:
                    player = self.player_service[message[user_id]]
                    if player:
                        player.lobby_connection.sendJSON(dict(command="social", autojoin=[channel]))

    async def check_user_login(self, cursor, login, password):
        # TODO: Hash passwords server-side so the hashing actually *does* something.
        await cursor.execute("SELECT login.id as id,"
                                  "login.login as username,"
                                  "login.password as password,"
                                  "login.steamid as steamid,"
                                  "login.create_time as create_time,"
                                  "lobby_ban.reason as reason,"
                                  "lobby_ban.expires_at as expires_at "
                                  "FROM login "
                                  "LEFT JOIN lobby_ban ON login.id = lobby_ban.idUser "
                                  "WHERE LOWER(login)=%s "
                                  "ORDER BY expires_at DESC", (login.lower(), ))

        if cursor.rowcount < 1:
            raise AuthenticationError("Login not found or password incorrect. They are case sensitive.")

        player_id, real_username, dbPassword, steamid, create_time, ban_reason, ban_expiry = await cursor.fetchone()

        if dbPassword != password:
            raise AuthenticationError("Login not found or password incorrect. They are case sensitive.")

        now = datetime.datetime.now()

        if ban_reason is not None and now < ban_expiry:
            self._logger.debug('Rejected login from banned user: %s, %s, %s', player_id, login, self.session)
            raise ClientError("You are banned from FAF.\n Reason :\n {}".format(ban_reason), recoverable=False)

        self._logger.debug("Login from: %s, %s, %s", player_id, login, self.session)

        return player_id, real_username, steamid


    def check_version(self, message):
        versionDB, updateFile = self.player_service.client_version_info
        update_msg = dict(command="update",
                          update=updateFile,
                          new_version=versionDB)

        self.user_agent = message.get('user_agent')
        version = message.get('version')
        server.stats.gauge('user.agents.None', -1, delta=True)
        server.stats.gauge('user.agents.{}'.format(self.user_agent), 1, delta=True)

        if not version or not self.user_agent:
            update_msg['command'] = 'welcome'
            # For compatibility with 0.10.x updating mechanism
            self.sendJSON(update_msg)
            return False

        # Check their client is reporting the right version number.
        if 'downlords-faf-client' not in self.user_agent:
            try:
                if "-" in version:
                    version = version.split('-')[0]
                if "+" in version:
                    version = version.split('+')[0]
                if semver.compare(versionDB, version) > 0:
                    self.sendJSON(update_msg)
                    return False
            except ValueError:
                self.sendJSON(update_msg)
                return False
        return True

    async def check_policy_conformity(self, player_id, uid_hash, session):
        url = FAF_POLICY_SERVER_BASE_URL + '/verify'
        payload = dict(player_id=player_id, uid_hash=uid_hash, session=session)
        headers = {
            'content-type': "application/json",
            'cache-control': "no-cache"
        }

        response = requests.post(url, json=payload, headers=headers).json()

        if response.get('result', '') == 'vm':
            self._logger.debug("Using VM: %d: %s", player_id, uid_hash)
            self.sendJSON(dict(command="notice", style="error",
                               text="You need to link your account to Steam in order to use FAF in a Virtual Machine. "
                                    "You can contact an admin on the forums."))

        if response.get('result', '') == 'already_associated':
            self._logger.warning("UID hit: %d: %s", player_id, uid_hash)
            self.send_warning("Your computer is already associated with another FAF account.<br><br>In order to "
                              "log in with a new account, you have to link it to Steam: <a href='" +
                              config.WWW_URL + "/account/link'>" +
                              config.WWW_URL + "/account/link</a>.<br>If you need an exception, please contact an "
                                               "admin on the forums", fatal=True)
            return False

        if response.get('result', '') == 'fraudulent':
            self._logger.info("Banning player %s for fraudulent looking login.", player_id)
            self.send_warning("Fraudulent login attempt detected. As a precautionary measure, your account has been "
                              "banned permanently. Please contact a moderator if you feel this is a false positive.",
                              fatal=True)

            with await db.db_pool as conn:
                try:
                    cursor = await conn.cursor()

                    await cursor.execute("INSERT INTO ban (player_id, author_id, reason, level) VALUES (%s, %s, %s, 'GLOBAL')",
                                         (player_id, player_id, "Auto-banned because of fraudulent login attempt"))
                except pymysql.MySQLError as e:
                    raise ClientError('Banning failed: {}'.format(e))

            return False

        return response.get('result', '') == 'honest'

    async def command_hello(self, message):
        login = message['login'].strip()
        password = message['password']

        async with db.db_pool.get() as conn:
            cursor = await conn.cursor()
            player_id, login, steamid = await self.check_user_login(cursor, login, password)
            server.stats.incr('user.logins')
            server.stats.gauge('users.online', len(self.player_service))

            await cursor.execute("UPDATE login SET ip = %(ip)s, user_agent = %(user_agent)s WHERE id = %(player_id)s", {
                                     "ip": self.peer_address.host,
                                     "user_agent": self.user_agent,
                                     "player_id": player_id
                                 })

            if not self.player_service.is_uniqueid_exempt(player_id) and steamid is None:
                conforms_policy = await self.check_policy_conformity(player_id, message['unique_id'], self.session)
                if not conforms_policy:
                    return


            # Update the user's IRC registration (why the f**k is this here?!)
            m = hashlib.md5()
            m.update(password.encode())
            passwordmd5 = m.hexdigest()
            m = hashlib.md5()
            # Since the password is hashed on the client, what we get at this point is really
            # md5(md5(sha256(password))). This is entirely insane.
            m.update(passwordmd5.encode())
            irc_pass = "******" + str(m.hexdigest())

            try:
                await cursor.execute("UPDATE anope.anope_db_NickCore SET pass = %s WHERE display = %s", (irc_pass, login))
            except (pymysql.OperationalError, pymysql.ProgrammingError):
                self._logger.error("Failure updating NickServ password for %s", login)

        permission_group = self.player_service.get_permission_group(player_id)
        self.player = Player(login=str(login),
                             session=self.session,
                             ip=self.peer_address.host,
                             port=None,
                             id=player_id,
                             permissionGroup=permission_group,
                             lobby_connection=self)
        self.connectivity = Connectivity(self, self.peer_address.host, self.player)

        if self.player.id in self.player_service and self.player_service[self.player.id].lobby_connection:
            old_conn = self.player_service[self.player.id].lobby_connection
            old_conn.send_warning("You have been signed out because you signed in elsewhere.", fatal=True)

        await self.player_service.fetch_player_data(self.player)

        self.player_service[self.player.id] = self.player
        self._authenticated = True

        # Country
        # -------
        try:
            self.player.country = str(gi.country(self.peer_address.host).country.iso_code)
        except (geoip2.errors.AddressNotFoundError,ValueError):
            self.player.country = ''

        ## AVATARS
        ## -------------------
        async with db.db_pool.get() as conn:
            cursor = await conn.cursor()
            await cursor.execute(
                "SELECT url, tooltip FROM `avatars` "
                "LEFT JOIN `avatars_list` ON `idAvatar` = `avatars_list`.`id` "
                "WHERE `idUser` = %s AND `selected` = 1", (self.player.id, ))
            avatar = await cursor.fetchone()
            if avatar:
                url, tooltip = avatar
                self.player.avatar = {"url": url, "tooltip": tooltip}

        # Send the player their own player info.
        self.sendJSON({
            "command": "welcome",
            "me": self.player.to_dict(),

            # For backwards compatibility for old clients. For now.
            "id": self.player.id,
            "login": login
        })

        # Tell player about everybody online. This must happen after "welcome".
        self.sendJSON(
            {
                "command": "player_info",
                "players": [player.to_dict() for player in self.player_service]
            }
        )

        # Tell everyone else online about us. This must happen after all the player_info messages.
        # This ensures that no other client will perform an operation that interacts with the
        # incoming user, allowing the client to make useful assumptions: it can be certain it has
        # initialised its local player service before it is going to get messages that want to
        # query it.
        self.player_service.mark_dirty(self.player)

        friends = []
        foes = []
        async with db.db_pool.get() as conn:
            cursor = await conn.cursor()
            await cursor.execute("SELECT `subject_id`, `status` "
                                 "FROM friends_and_foes WHERE user_id = %s", (self.player.id,))

            for target_id, status in await cursor.fetchall():
                if status == "FRIEND":
                    friends.append(target_id)
                else:
                    foes.append(target_id)

        self.player.friends = set(friends)
        self.player.foes = set(foes)

        channels = []
        if self.player.mod:
            channels.append("#moderators")

        if self.player.clan is not None:
            channels.append("#%s_clan" % self.player.clan)

        jsonToSend = {"command": "social", "autojoin": channels, "channels": channels, "friends": friends, "foes": foes, "power": permission_group}
        self.sendJSON(jsonToSend)

        self.send_mod_list()
        self.send_game_list()
        self.send_tutorial_section()

    @timed
    def command_ask_session(self, message):
        if self.check_version(message):
            self.sendJSON({
                "command": "session",
                "session": self.session
            })

    async def command_avatar(self, message):
        action = message['action']

        if action == "list_avatar":
            avatarList = []

            async with db.db_pool.get() as conn:
                cursor = await conn.cursor()
                await cursor.execute(
                    "SELECT url, tooltip FROM `avatars` "
                    "LEFT JOIN `avatars_list` ON `idAvatar` = `avatars_list`.`id` WHERE `idUser` = %s", (self.player.id,))

                avatars = await cursor.fetchall()
                for url, tooltip in avatars:
                    avatar = {"url": url, "tooltip": tooltip}
                    avatarList.append(avatar)

                if len(avatarList) > 0:
                    self.sendJSON({"command": "avatar", "avatarlist": avatarList})

        elif action == "select":
            avatar = message['avatar']

            async with db.db_pool.get() as conn:
                cursor = await conn.cursor()
                await cursor.execute(
                    "UPDATE `avatars` SET `selected` = 0 WHERE `idUser` = %s", (self.player.id, ))
                if avatar is not None:
                    await cursor.execute(
                        "UPDATE `avatars` SET `selected` = 1 WHERE `idAvatar` ="
                        "(SELECT id FROM avatars_list WHERE avatars_list.url = %s) and "
                        "`idUser` = %s", (avatar, self.player.id))
        else:
            raise KeyError('invalid action')

    @property
    def able_to_launch_game(self):
        return self.connectivity.result

    @timed
    def command_game_join(self, message):
        """
        We are going to join a game.
        """
        assert isinstance(self.player, Player)
        if not self.able_to_launch_game:
            raise ClientError("You are already in a game or haven't run the connectivity test yet")

        if self.connectivity.result.state == ConnectivityState.STUN:
            self.connectivity.relay_address = Address(*message['relay_address'])

        uuid = message['uid']
        port = message['gameport']
        password = message.get('password', None)

        self._logger.debug("joining: %d:%d with pw: %s", uuid, port, password)
        try:
            game = self.game_service[uuid]
            if not game or game.state != GameState.LOBBY:
                self._logger.debug("Game not in lobby state: %s", game)
                self.sendJSON(dict(command="notice", style="info", text="The game you are trying to join is not ready."))
                return

            if game.password != password:
                self.sendJSON(dict(command="notice", style="info", text="Bad password (it's case sensitive)"))
                return

            self.launch_game(game, port, False)
        except KeyError:
            self.sendJSON(dict(command="notice", style="info", text="The host has left the game"))


    @asyncio.coroutine
    def command_game_matchmaking(self, message):
        mod = message.get('mod', 'ladder1v1')
        port = message.get('gameport', None)
        state = message['state']

        if not self.able_to_launch_game:
            raise ClientError("You are already in a game or are otherwise having connection problems. Please report this issue using HELP -> Tech support.")

        if state == "stop":
            if self.search:
                self._logger.info("%s stopped searching for ladder: %s", self.player, self.search)
                self.search.cancel()
            return

        if self.connectivity.result.state == ConnectivityState.STUN:
            self.connectivity.relay_address = Address(*message['relay_address'])

        if port:
            self.player.game_port = port

        with (yield from db.db_pool) as conn:
            cursor = yield from conn.cursor()
            yield from cursor.execute("SELECT id FROM matchmaker_ban WHERE `userid` = %s", (self.player.id))
            if cursor.rowcount > 0:
                self.sendJSON(dict(command="notice", style="error",
                                   text="You are banned from the matchmaker. Contact an admin to have the reason."))
                return

        if mod == "ladder1v1":
            if state == "start":
                if self.search:
                    self.search.cancel()
                assert self.player is not None
                self.search = Search(self.player)
                self.player.faction = message['faction']

                self.game_service.ladder_service.inform_player(self.player)

                self._logger.info("%s is searching for ladder: %s", self.player, self.search)
                asyncio.ensure_future(self.player_service.ladder_queue.search(self.player, search=self.search))

    def command_coop_list(self, message):
        """ Request for coop map list"""
        asyncio.ensure_future(self.send_coop_maps())

    @timed()
    def command_game_host(self, message):
        if not self.able_to_launch_game:
            raise ClientError("You are already in a game or haven't run the connectivity test yet")

        if self.connectivity.result.state == ConnectivityState.STUN:
            self.connectivity.relay_address = Address(*message['relay_address'])

        assert isinstance(self.player, Player)

        title = cgi.escape(message.get('title', ''))
        port = message.get('gameport')
        visibility = VisibilityState.from_string(message.get('visibility'))
        if not isinstance(visibility, VisibilityState):
            # Protocol violation.
            self.abort("%s sent a nonsense visibility code: %s" % (self.player.login, message.get('visibility')))
            return

        mod = message.get('mod')
        try:
            title.encode('ascii')
        except UnicodeEncodeError:
            self.sendJSON(dict(command="notice", style="error", text="Non-ascii characters in game name detected."))
            return

        mapname = message.get('mapname')
        password = message.get('password')

        game = self.game_service.create_game(**{
            'visibility': visibility,
            'game_mode': mod.lower(),
            'host': self.player,
            'name': title if title else self.player.login,
            'mapname': mapname,
            'password': password
        })
        self.launch_game(game, port, True)
        server.stats.incr('game.hosted')

    def launch_game(self, game, port, is_host=False, use_map=None):
        # FIXME: Setting up a ridiculous amount of cyclic pointers here
        if self.game_connection:
            self.game_connection.abort("Player launched a new game")
        self.game_connection = GameConnection(self.loop,
                                              self,
                                              self.player_service,
                                              self.game_service)
        self.game_connection.player = self.player
        self.player.game_connection = self.game_connection
        self.game_connection.game = game
        if is_host:
            game.host = self.player

        self.player.state = PlayerState.HOSTING if is_host else PlayerState.JOINING
        self.player.game = game
        self.player.game_port = port
        cmd = {"command": "game_launch",
                       "mod": game.game_mode,
                       "uid": game.id,
                       "args": ["/numgames " + str(self.player.numGames)]}
        if use_map:
            cmd['mapname'] = use_map
        self.sendJSON(cmd)

    @asyncio.coroutine
    def command_modvault(self, message):
        type = message["type"]

        with (yield from db.db_pool) as conn:
            cursor = yield from conn.cursor()
            if type == "start":
                yield from cursor.execute("SELECT uid, name, version, author, ui, date, downloads, likes, played, description, filename, icon FROM table_mod ORDER BY likes DESC LIMIT 100")

                for i in range(0, cursor.rowcount):
                    uid, name, version, author, ui, date, downloads, likes, played, description, filename, icon = yield from cursor.fetchone()
                    try:
                        link = urllib.parse.urljoin(config.CONTENT_URL, "faf/vault/" + filename)
                        thumbstr = ""
                        if icon != "":
                            thumbstr = urllib.parse.urljoin(config.CONTENT_URL, "faf/vault/mods_thumbs/" + urllib.parse.quote(icon))

                        out = dict(command="modvault_info", thumbnail=thumbstr, link=link, bugreports=[],
                                   comments=[], description=description, played=played, likes=likes,
                                   downloads=downloads, date=int(date.timestamp()), uid=uid, name=name, version=version, author=author,
                                   ui=ui)
                        self.sendJSON(out)
                    except:
                        self._logger.error("Error handling table_mod row (uid: {})".format(uid), exc_info=True)
                        pass


            elif type == "like":
                canLike = True
                uid = message['uid']
                yield from cursor.execute("SELECT uid, name, version, author, ui, date, downloads, likes, played, description, filename, icon, likers FROM `table_mod` WHERE uid = %s LIMIT 1", (uid,))

                uid, name, version, author, ui, date, downloads, likes, played, description, filename, icon, likerList = yield from cursor.fetchone()
                link = urllib.parse.urljoin(config.CONTENT_URL, "faf/vault/" + filename)
                thumbstr = ""
                if icon != "":
                    thumbstr = urllib.parse.urljoin(config.CONTENT_URL, "faf/vault/mods_thumbs/" + urllib.parse.quote(icon))

                out = dict(command="modvault_info", thumbnail=thumbstr, link=link, bugreports=[],
                           comments=[], description=description, played=played, likes=likes + 1,
                           downloads=downloads, date=int(date.timestamp()), uid=uid, name=name, version=version, author=author,
                           ui=ui)

                try:
                    likers = json.loads(likerList)
                    if self.player.id in likers:
                        canLike = False
                    else:
                        likers.append(self.player.id)
                except:
                    likers = []

                # TODO: Avoid sending all the mod info in the world just because we liked it?
                if canLike:
                    yield from cursor.execute("UPDATE mod_stats s "
                                              "JOIN mod_version v ON v.mod_id = s.mod_id "
                                              "SET s.likes = s.likes + 1, likers=%s WHERE v.uid = %s",
                                              json.dumps(likers), uid)
                    self.sendJSON(out)

            elif type == "download":
                uid = message["uid"]
                yield from cursor.execute("UPDATE mod_stats s "
                                          "JOIN mod_version v ON v.mod_id = s.mod_id "
                                          "SET downloads=downloads+1 WHERE v.uid = %s", uid)
            else:
                raise ValueError('invalid type argument')

    def send_warning(self, message: str, fatal: bool=False):
        """
        Display a warning message to the client
        :param message: Warning message to display
        :param fatal: Whether or not the warning is fatal.
                      If the client receives a fatal warning it should disconnect
                      and not attempt to reconnect.
        :return: None
        """
        self.sendJSON({'command': 'notice',
                       'style': 'info' if not fatal else 'error',
                       'text': message})
        if fatal:
            self.abort(message)

    def send(self, message):
        """

        :param message:
        :return:
        """
        self._logger.debug(">>: %s", message)
        self.protocol.send_message(message)

    async def drain(self):
        await self.protocol.drain()

    def sendJSON(self, data_dictionary):
        """
        Deprecated alias for send
        """
        self.send(data_dictionary)

    async def on_connection_lost(self):
        async def nopdrain(message):
            return
        self.drain = nopdrain
        self.send = lambda m: None
        if self.game_connection:
            await self.game_connection.on_connection_lost()
        if self.search and not self.search.done():
            self.search.cancel()
        if self.player:
            self.player_service.remove_player(self.player)
Esempio n. 37
0
def test_equality_by_id():
    p = Player('Sheeo', 42)
    p2 = Player('RandomSheeo', 42)
    assert p == p2
    assert p.__hash__() == p2.__hash__()
Esempio n. 38
0
def player():
    player = Player(login="******", id=42)
    player._lobby_connection = MagicMock(spec=LobbyConnection)
    return player