3131)
3232from .decorators import with_logger
3333from .game_service import GameService
34- from .games import InitMode , LadderGame
34+ from .games import Game , InitMode , LadderGame
3535from .matchmaker import MapPool , MatchmakerQueue , OnMatchedCallback , Search
3636from .players import Player , PlayerState
37+ from .protocol import DisconnectedError
3738from .types import GameLaunchOptions , Map , NeroxisGeneratedMap
3839
3940
41+ class GameLaunchError (Exception ):
42+ """When a game failed to launch because some players did not connect"""
43+
44+ def __init__ (self , players : List [Player ]):
45+ self .players = players
46+
47+
4048@with_logger
4149class LadderService (Service ):
4250 """
@@ -427,41 +435,7 @@ def get_player_mean(player):
427435 game .set_player_option (player .id , "Army" , slot )
428436 game .set_player_option (player .id , "Color" , slot )
429437
430- self ._logger .debug ("Starting ladder game: %s" , game )
431- # Options shared by all players
432- options = GameLaunchOptions (
433- mapname = game .map_name ,
434- expected_players = len (all_players ),
435- )
436-
437- def game_options (player : Player ) -> GameLaunchOptions :
438- return options ._replace (
439- team = game .get_player_option (player .id , "Team" ),
440- faction = player .faction ,
441- map_position = game .get_player_option (player .id , "StartSpot" )
442- )
443-
444- await host .lobby_connection .launch_game (
445- game , is_host = True , options = game_options (host )
446- )
447- try :
448- await game .wait_hosted (60 )
449- finally :
450- # TODO: Once the client supports `match_cancelled`, don't
451- # send `launch_game` to the client if the host timed out. Until
452- # then, failing to send `launch_game` will cause the client to
453- # think it is searching for ladder, even though the server has
454- # already removed it from the queue.
455-
456- await asyncio .gather (* [
457- guest .lobby_connection .launch_game (
458- game , is_host = False , options = game_options (guest )
459- )
460- for guest in all_guests
461- if guest .lobby_connection is not None
462- ])
463- await game .wait_launched (60 + 10 * len (all_guests ))
464- self ._logger .debug ("Ladder game launched successfully" )
438+ await self .launch_game (game , host , all_guests )
465439 except Exception :
466440 if game :
467441 await game .on_game_end ()
@@ -473,6 +447,74 @@ def game_options(player: Player) -> GameLaunchOptions:
473447 player .state = PlayerState .IDLE
474448 player .write_message (msg )
475449
450+ async def launch_game (
451+ self ,
452+ game : Game ,
453+ host : Player ,
454+ guests : List [Player ]
455+ ) -> None :
456+ self ._logger .debug ("Starting ladder game: %s" , game )
457+ all_players = (host , * guests )
458+
459+ # Options shared by all players
460+ options = GameLaunchOptions (
461+ mapname = game .map_name ,
462+ expected_players = len (all_players ),
463+ )
464+
465+ def game_options (player : Player ) -> GameLaunchOptions :
466+ return options ._replace (
467+ team = game .get_player_option (player .id , "Team" ),
468+ faction = player .faction ,
469+ map_position = game .get_player_option (player .id , "StartSpot" )
470+ )
471+
472+ # Check if anyone DC'd from the server entirely
473+ disconnected_players = [
474+ player for player in all_players
475+ if player .lobby_connection is None
476+ ]
477+ if disconnected_players :
478+ raise GameLaunchError (disconnected_players )
479+
480+ try :
481+ await host .lobby_connection .launch_game (
482+ game ,
483+ is_host = True ,
484+ options = game_options (host )
485+ )
486+ await game .wait_hosted (60 )
487+ except (asyncio .TimeoutError , DisconnectedError ) as e :
488+ raise GameLaunchError ([host ]) from e
489+ finally :
490+ # TODO: Once the client supports `match_cancelled`, don't
491+ # send `launch_game` to the client if the host timed out. Until
492+ # then, failing to send `launch_game` will cause the client to
493+ # think it is searching for ladder, even though the server has
494+ # already removed it from the queue.
495+
496+ await asyncio .gather (* [
497+ guest .lobby_connection .launch_game (
498+ game ,
499+ is_host = False ,
500+ options = game_options (guest )
501+ )
502+ for guest in guests
503+ if guest .lobby_connection is not None
504+ ], return_exceptions = True )
505+
506+ try :
507+ await game .wait_launched (60 + 10 * len (guests ))
508+ except asyncio .TimeoutError as e :
509+ connected_players = game .players
510+ disconnected_players = [
511+ player for player in all_players
512+ if player not in connected_players
513+ ]
514+ raise GameLaunchError (disconnected_players ) from e
515+
516+ self ._logger .debug ("Ladder game launched successfully" )
517+
476518 async def get_game_history (
477519 self ,
478520 players : List [Player ],
0 commit comments