diff --git a/server/factions.py b/server/factions.py index 6ce5cae3a..a2ceee22e 100644 --- a/server/factions.py +++ b/server/factions.py @@ -1,4 +1,5 @@ from enum import IntEnum, unique +from typing import Union @unique @@ -13,4 +14,13 @@ class Faction(IntEnum): @staticmethod def from_string(value: str) -> "Faction": - return Faction.__members__[value] + return Faction.__members__[value.lower()] + + @staticmethod + def from_value(value: Union[str, int]) -> "Faction": + if isinstance(value, str): + return Faction.from_string(value) + elif isinstance(value, int): + return Faction(value) + + raise TypeError(f"Unsupported faction type {type(value)}!") diff --git a/server/ladder_service.py b/server/ladder_service.py index 60c414fa4..cc52bad88 100644 --- a/server/ladder_service.py +++ b/server/ladder_service.py @@ -30,7 +30,7 @@ from .decorators import with_logger from .game_service import GameService from .games import LadderGame -from .matchmaker import MapPool, MatchmakerQueue, Search +from .matchmaker import MapPool, MatchmakerQueue, OnMatchedCallback, Search from .players import Player, PlayerState from .protocol import DisconnectedError from .rating import RatingType @@ -165,14 +165,23 @@ async def fetch_matchmaker_queues(self, conn): )) return matchmaker_queues - def start_search(self, players: List[Player], queue_name: str): + def start_search( + self, + players: List[Player], + queue_name: str, + on_matched: OnMatchedCallback = lambda _1, _2: None + ): # Cancel any existing searches that players have for this queue for player in players: if queue_name in self._searches[player]: self._cancel_search(player, queue_name) queue = self.queues[queue_name] - search = Search(players, rating_type=queue.rating_type) + search = Search( + players, + rating_type=queue.rating_type, + on_matched=on_matched + ) for player in players: player.state = PlayerState.SEARCHING_LADDER diff --git a/server/lobbyconnection.py b/server/lobbyconnection.py index 0a18adfc5..ee21e2ccb 100644 --- a/server/lobbyconnection.py +++ b/server/lobbyconnection.py @@ -855,16 +855,17 @@ async def command_game_matchmaking(self, message): recoverable=True ) - for member in party: - member.set_player_faction() - # TODO: Remove this legacy behavior, use party instead if "faction" in message: - self.player.faction = message["faction"] + party.set_factions( + self.player, + [Faction.from_value(message["faction"])] + ) self.ladder_service.start_search( players, - queue_name=queue_name + queue_name=queue_name, + on_matched=party.on_matched ) @ice_only @@ -1075,7 +1076,7 @@ async def command_leave_party(self, _message): await self.party_service.leave_party(self.player) async def command_set_party_factions(self, message): - factions = set(Faction.from_string(str(i).lower()) for i in message["factions"]) + factions = set(Faction.from_value(v) for v in message["factions"]) if not factions: raise ClientError( diff --git a/server/matchmaker/__init__.py b/server/matchmaker/__init__.py index 7ae8c9261..63068ebdf 100644 --- a/server/matchmaker/__init__.py +++ b/server/matchmaker/__init__.py @@ -7,12 +7,13 @@ from .map_pool import MapPool from .matchmaker_queue import MatchmakerQueue from .pop_timer import PopTimer -from .search import CombinedSearch, Search +from .search import CombinedSearch, OnMatchedCallback, Search __all__ = ( "CombinedSearch", "MapPool", "MatchmakerQueue", + "OnMatchedCallback", "PopTimer", "Search", ) diff --git a/server/matchmaker/search.py b/server/matchmaker/search.py index f6ff63f29..28e632d26 100644 --- a/server/matchmaker/search.py +++ b/server/matchmaker/search.py @@ -2,7 +2,7 @@ import itertools import math import time -from typing import List, Optional, Tuple +from typing import Any, Callable, List, Optional, Tuple from trueskill import Rating, quality @@ -12,6 +12,9 @@ from ..decorators import with_logger from ..players import Player +Match = Tuple["Search", "Search"] +OnMatchedCallback = Callable[["Search", "Search"], Any] + @with_logger class Search: @@ -23,7 +26,8 @@ def __init__( self, players: List[Player], start_time: Optional[float] = None, - rating_type: str = RatingType.LADDER_1V1 + rating_type: str = RatingType.LADDER_1V1, + on_matched: OnMatchedCallback = lambda _1, _2: None ): """ Default ctor for a search @@ -42,6 +46,7 @@ def __init__( self.start_time = start_time or time.time() self._match = asyncio.Future() self._failed_matching_attempts = 0 + self.on_matched = on_matched # Precompute this self.quality_against_self = self.quality_with(self) @@ -201,6 +206,8 @@ def match(self, other: "Search"): """ self._logger.info("Matched %s with %s", self.players, other.players) + self.on_matched(self, other) + for player, raw_rating in zip(self.players, self.raw_ratings): if self.is_ladder1v1_search() and self._is_ladder_newbie(player): mean, dev = raw_rating @@ -307,6 +314,3 @@ def cancel(self): def __str__(self): return "CombinedSearch({})".format(",".join(str(s) for s in self.searches)) - - -Match = Tuple[Search, Search] diff --git a/server/players.py b/server/players.py index aecf95572..fc1ff2617 100644 --- a/server/players.py +++ b/server/players.py @@ -81,14 +81,10 @@ def faction(self) -> Faction: @faction.setter def faction(self, value: Union[str, int, Faction]) -> None: - if isinstance(value, str): - self._faction = Faction.from_string(value) - elif isinstance(value, int): - self._faction = Faction(value) - elif isinstance(value, Faction): + if isinstance(value, Faction): self._faction = value else: - raise TypeError(f"Unsupported faction type {type(value)}!") + self._faction = Faction.from_value(value) def power(self) -> int: """An artifact of the old permission system. The client still uses this diff --git a/server/team_matchmaker/player_party.py b/server/team_matchmaker/player_party.py index cdf6f3860..cab314a3e 100644 --- a/server/team_matchmaker/player_party.py +++ b/server/team_matchmaker/player_party.py @@ -2,6 +2,7 @@ from typing import FrozenSet, List, NamedTuple, Optional from server.factions import Faction +from server.matchmaker import Search from server.players import Player from server.team_matchmaker.party_member import PartyMember @@ -68,6 +69,10 @@ def remove_invited_player(self, player: Player) -> None: def set_factions(self, player: Player, factions: List[Faction]) -> None: self._members[player].factions = factions + def on_matched(self, search1: Search, search2: Search) -> None: + for member in self: + member.set_player_faction() + async def send_party(self, player: Player) -> None: await player.send_message({ "command": "update_party", diff --git a/tests/integration_tests/test_teammatchmaker.py b/tests/integration_tests/test_teammatchmaker.py index 245f25127..330291086 100644 --- a/tests/integration_tests/test_teammatchmaker.py +++ b/tests/integration_tests/test_teammatchmaker.py @@ -141,7 +141,7 @@ async def test_game_matchmaking_with_parties(lobby_server): await proto1.send_message({ "command": "set_party_factions", - "factions": ["uef"] + "factions": ["seraphim"] }) await proto2.send_message({ "command": "set_party_factions", @@ -164,6 +164,11 @@ async def test_game_matchmaking_with_parties(lobby_server): "queue_name": "tmm2v2", "state": "start", }) + # Change faction selection after queueing + await proto1.send_message({ + "command": "set_party_factions", + "factions": ["uef"] + }) await proto3.send_message({ "command": "game_matchmaking", "queue_name": "tmm2v2",