class DotaBot(Greenlet): """A worker thread, connecting to steam to process a unique job. Attributes: worker_manager: `DazzarWorkerManager` this bot is linked to. credential: `Credential` used to connect to steam. """ def __init__(self, worker_manager, credential, job): """Initialize the Dota bot thread for a unique job process. Args: worker_manager: `DazzarWorkerManager` this bot is linked to. credential: `Credential` used to connect to steam. job: `Job` to process by the bot. """ Greenlet.__init__(self) self.credential = credential self.worker_manager = worker_manager self.job = job self.client = SteamClient() self.dota = dota2.Dota2Client(self.client) self.app = self.worker_manager.app self.job_started = False self.game_creation_call = False self.job_finished = False self.match = None self.players = None self.game_status = None self.lobby_channel_id = None self.invite_timer = None self.missing_players = None self.missing_players_count = None self.wrong_team_players = None self.wrong_team_players_count = None # Prepare all event handlers # - Steam client events # - Dota client events # - Dazzar bot events self.client.on('connected', self.steam_connected) self.client.on('logged_on', self.steam_logged) self.dota.on('ready', self.dota_ready) self.dota.on('notready', self.closed_dota) self.dota.on('profile_card', self.scan_profile_result) self.dota.on('player_info', self.scan_player_info) self.dota.on(dota2.features.Lobby.EVENT_LOBBY_NEW, self.vip_game_created) self.dota.on(dota2.features.Lobby.EVENT_LOBBY_CHANGED, self.game_update) self.dota.on(dota2.features.Chat.EVENT_CHANNEL_JOIN, self.channel_join) self.dota.on(dota2.features.Chat.EVENT_CHANNEL_MESSAGE, self.channel_message) def _run(self): """Start the main loop of the thread, connecting to Steam, waiting for the job to finish to close the bot.""" self.print_info('Connecting to Steam...') self.client.connect(retry=None) # Try connecting with infinite retries while not self.job_finished: sleep(10) self.client.disconnect() self.worker_manager.bot_end(self.credential) # Helpers def print_info(self, trace): """Wrapper of `logging.info` with bot name prefix. Args: trace: String to output as INFO. """ logging.info('%s: %s', self.credential.login, trace) def print_error(self, trace): """Wrapper of `logging.error` with bot name prefix. Args: trace: String to output as ERROR. """ logging.error("%s: %s", self.credential.login, trace) # Callback of Steam and Dota clients def steam_connected(self): """Callback fired when the bot is connected to Steam, login user.""" self.print_info('Connected to Steam.') self.client.login(self.credential.login, self.credential.password) def steam_logged(self): """Callback fired when the bot is logged into Steam, starting Dota.""" self.print_info('Logged to Steam.') self.dota.launch() def dota_ready(self): """Callback fired when the Dota application is ready, resume the job processing.""" self.print_info('Dota application is ready.') self.compute_job() def closed_dota(self): """Callback fired when the Dota application is closed.""" self.print_info('Dota application is closed.') def compute_job(self): """Start the processing of the job with the appropriate handler.""" self.print_info('Processing new job of type %s' % type(self.job)) if not self.job_started: self.job_started = True else: return if type(self.job) is JobScan: self.scan_profile() elif type(self.job) is JobCreateGame: self.vip_game() else: self.end_job_processing() def end_job_processing(self): """Mark the job as finished, preparing the bot to close.""" self.print_info('Job ended.') self.job = None self.job_finished = True def channel_join(self, channel_info): if channel_info.channel_type != dota2.enums.DOTAChatChannelType_t.DOTAChannelType_Lobby: self.dota.leave_channel(channel_info.channel_id) else: if self.game_status is not None: if channel_info.channel_name == 'Lobby_{0}'.format( self.game_status.lobby_id): self.lobby_channel_id = channel_info.channel_id def channel_message(self, message): pass ############################ # Scan profile job section # ############################ def scan_profile(self): """Start the process of the job as a profile scan, request the information from Steam.""" while not self.job.scan_finish: self.print_info('Requesting profile for user %s' % self.job.steam_id) user = SteamID(self.job.steam_id) self.dota.request_profile_card(user.as_32) # We give the task 30 sec to finish or retry sleep(30) if self.job.scan_finish: self.end_job_processing() def scan_player_info(self, message): self.print_info(message) self.job.scan_finish = True def scan_profile_result(self, account_id, profile_card): """Process the profile information returned by Steam. Extract the SoloMMR from the profile and insert the user in the good ladder. Args: account_id: steam_id (as 32bits) of the profile result profile_card: profile information as a protobuff message """ self.print_info('Processing profile of user %s' % SteamID(account_id).as_64) solo_mmr = None for slot in profile_card.slots: if not slot.HasField('stat'): continue if slot.stat.stat_id != 1: continue solo_mmr = int(slot.stat.stat_score) with self.app.app_context(): user = User.query.filter_by(id=self.job.steam_id).first() user.profile_scan_info.last_scan = datetime.utcnow() if solo_mmr is not None: user.solo_mmr = solo_mmr if user.solo_mmr is None: user.section = None else: if user.solo_mmr > 4500: user.section = constants.LADDER_HIGH else: user.section = constants.LADDER_LOW db.session.commit() self.job.scan_finish = True ######################## # VIP game job section # ######################## def vip_game(self): """Start the process of the job as a game creation.""" self.print_info('Hosting game %s' % self.job.match_id) # Copy the match data from the database with self.app.app_context(): self.match = Match.query.filter_by(id=self.job.match_id).first() if self.match is None or self.match.status != constants.MATCH_STATUS_CREATION: self.dota.leave_practice_lobby() self.end_job_processing() else: self.players = {} for player in PlayerInMatch.query.\ options(joinedload_all('player')).\ filter(PlayerInMatch.match_id == self.job.match_id).\ all(): self.players[player.player_id] = player db.session.expunge(self.match) for player_id, player in self.players.items(): db.session.expunge(player) # Start the Dota lobby self.dota.create_practice_lobby(password=self.match.password) self.game_creation_call = True def vip_game_created(self, message): """Callback fired when the Dota bot enters a lobby. Args: message: first lobby information """ self.game_status = message if self.job is None or not self.game_creation_call: self.dota.leave_practice_lobby() else: self.initialize_lobby() start = self.manage_player_waiting() if not start: self.dota.send_message(self.lobby_channel_id, 'Annulation de la partie.') self.process_game_dodge() else: self.dota.send_message(self.lobby_channel_id, 'Tous les joueurs sont présents.') self.start_game() # Waiting PostGame = 3 or UI = 0 (means no loading) while self.game_status.state != 0 and self.game_status.state != 3: sleep(5) if self.game_status.state == 0: self.process_game_dodge() elif self.game_status.state == 3: self.process_endgame_results() if self.lobby_channel_id is not None: self.dota.leave_channel(self.lobby_channel_id) self.lobby_channel_id = None self.dota.leave_practice_lobby() self.end_job_processing() def game_update(self, message): """Callback fired when the game lobby change, update local information.""" self.game_status = message def initialize_lobby(self): """Setup the game lobby with the good options, and change status in database.""" self.print_info('Game %s created, setup.' % self.job.match_id) self.dota.join_lobby_channel() self.dota.join_practice_lobby_team() game_mode_encoding = { 'ap': dota2.enums.DOTA_GameMode.DOTA_GAMEMODE_ALL_DRAFT, 'rd': dota2.enums.DOTA_GameMode.DOTA_GAMEMODE_RD, 'cd': dota2.enums.DOTA_GameMode.DOTA_GAMEMODE_CD } options = { 'game_name': 'Dazzar Game {0}'.format(str(self.match.id)), 'pass_key': self.match.password, 'game_mode': game_mode_encoding[self.match.mode], 'server_region': int(dota2.enums.EServerRegion.Europe), 'fill_with_bots': False, 'allow_spectating': True, 'allow_cheats': False, 'allchat': False, 'dota_tv_delay': 2, 'pause_setting': 1 } self.dota.config_practice_lobby(options=options) with self.app.app_context(): match = Match.query.filter_by(id=self.job.match_id).first() match.status = constants.MATCH_STATUS_WAITING_FOR_PLAYERS db.session.commit() def manage_player_waiting(self): """Wait for players to join the lobby with actions depending on player actions. Returns: A boolean that indicates if the game should be started after the player waiting process. """ self.invite_timer = timedelta(minutes=5) self.compute_player_status() refresh_rate = 10 while self.invite_timer != timedelta(0): for player in self.missing_players: self.dota.invite_to_lobby(player) sleep(refresh_rate) self.compute_player_status() if len(self.missing_players) == 0 and len( self.wrong_team_players) == 0: return True else: if self.invite_timer.seconds != 0 and self.invite_timer.seconds % 60 in [ 0, 30 ]: minutes = self.invite_timer.seconds // 60 seconds = self.invite_timer.seconds - 60 * minutes self.dota.send_message( self.lobby_channel_id, '{:01d}:{:02d} avant annulation, {} absent(s), {} mal placé(s).' .format(minutes, seconds, self.missing_players_count, self.wrong_team_players_count)) self.invite_timer = self.invite_timer - timedelta( seconds=refresh_rate) return False def compute_player_status(self): """Helpers to manage player status from protobuff message. Invite all missing players to come to the lobby. Kick all players not supposed to be inside a lobby. Kick from slots all players not in the good slot. """ self.missing_players = [] self.missing_players_count = 0 self.wrong_team_players = [] self.wrong_team_players_count = 0 for player_id, player in self.players.items(): self.missing_players_count += 1 self.missing_players.append(player_id) for message_player in self.game_status.members: if message_player.id == self.dota.steam_id: continue if message_player.id in self.missing_players: self.missing_players_count -= 1 self.missing_players.remove(message_player.id) good_slot = message_player.slot == self.players[ message_player.id].team_slot good_team = (message_player.team == DOTA_GC_TEAM.GOOD_GUYS and self.players[message_player.id].is_radiant) or \ (message_player.team == DOTA_GC_TEAM.BAD_GUYS and not self.players[message_player.id].is_radiant) if not (good_team and good_slot): self.wrong_team_players.append(message_player.id) self.wrong_team_players_count += 1 if message_player.team != DOTA_GC_TEAM.PLAYER_POOL: self.dota.practice_lobby_kick_from_team( SteamID(message_player.id).as_32) else: # Say: Kick joueur non authorisé message_player.name self.dota.practice_lobby_kick(SteamID(message_player.id).as_32) def process_game_dodge(self): """Punish players stopping game start.""" self.print_info('Game %s cancelled because of dodge.' % self.job.match_id) # Say: Partie annulée - punish with self.app.app_context(): match = Match.query.filter_by(id=self.job.match_id).first() match.status = constants.MATCH_STATUS_CANCELLED self.compute_player_status() for player in PlayerInMatch.query. \ options(joinedload_all('player')). \ filter(PlayerInMatch.match_id == self.job.match_id). \ all(): if player.player.current_match == self.job.match_id: player.player.current_match = None # Update Scoreboard if player.player_id in self.missing_players or player.player_id in self.wrong_team_players: score = Scoreboard.query.filter_by( ladder_name=match.section, user_id=player.player_id).first() if score is None: score = Scoreboard(user=player.player, ladder_name=match.section) db.session.add(score) player.is_dodge = True score.points -= 2 score.dodge += 1 db.session.commit() def start_game(self): """Start the Dota game and update status in database.""" self.print_info('Launching game %s' % self.job.match_id) self.dota.launch_practice_lobby() sleep(10) with self.app.app_context(): match = Match.query.filter_by(id=self.job.match_id).first() match.status = constants.MATCH_STATUS_IN_PROGRESS if self.game_status.connect is not None and self.game_status.connect[ 0:1] == '=[': match.server = self.game_status.connect[2:-1] elif self.game_status.server_id is not None: match.server = self.game_status.server_id db.session.commit() sleep(10) def process_endgame_results(self): """After a game, process lobby results into database.""" self.print_info('Game %s over.' % self.job.match_id) with self.app.app_context(): match = Match.query.filter_by(id=self.job.match_id).first() match.status = constants.MATCH_STATUS_ENDED match.server = None if self.game_status.match_outcome == 2: match.radiant_win = True elif self.game_status.match_outcome == 3: match.radiant_win = False else: match.radiant_win = None self.players = {} for player in PlayerInMatch.query. \ options(joinedload_all('player')). \ filter(PlayerInMatch.match_id == self.job.match_id). \ all(): if player.player.current_match == self.job.match_id: player.player.current_match = None self.players[player.player_id] = player # Process scoreboard updates for player_id, player in self.players.items(): score = Scoreboard.query.filter_by(ladder_name=match.section, user_id=player_id).first() if score is None: score = Scoreboard(user=player.player, ladder_name=match.section) db.session.add(score) score.matches += 1 for player in self.game_status.members: if player.id == self.dota.steam_id: continue id = player.id score = Scoreboard.query.filter_by(ladder_name=match.section, user_id=id).first() if (self.players[id].is_radiant and self.game_status.match_outcome == 2) or \ (not self.players[id].is_radiant and self.game_status.match_outcome == 3): score.points += 1 score.win += 1 elif (self.players[id].is_radiant and self.game_status.match_outcome == 3) or \ (not self.players[id].is_radiant and self.game_status.match_outcome == 2): score.loss += 1 for player in self.game_status.left_members: score = Scoreboard.query.filter_by(ladder_name=match.section, user_id=player.id).first() self.players[player.id].is_leaver = True score.points -= 3 score.leave += 1 db.session.commit()
user_password = "******" logging.basicConfig(format='[%(asctime)s] %(levelname)s %(name)s: %(message)s', level=logging.DEBUG) client = SteamClient() dota = Dota2Client(client) login_called = False @client.on('connected') def login(): global login_called if not login_called: client.login(user_name, user_password) login_called = True @client.on(client.EVENT_AUTH_CODE_REQUIRED) def auth_code_prompt(is_2fa, code_mismatch): if is_2fa: code = input("Enter 2FA Code: ") client.login(user_name, user_password, two_factor_code=code) else: code = input("Enter Email Code: ") client.login(user_name, user_password, auth_code=code) @client.on('logged_on') def start_dota(): dota.launch() client.connect() client.run_forever()
class DotaBot(Greenlet): """A worker thread, connecting to steam to process a unique job. Attributes: worker_manager: `WorkerManager` this bot is linked to. credential: `Credential` used to connect to steam. """ def __init__(self, worker_manager, credential, admins, casters, id, name, password, team1, team2, team1_ids, team2_ids, team_choosing_first): """Initialize the Dota bot thread for a unique job process. Args: worker_manager: `DazzarWorkerManager` this bot is linked to. credential: `Credential` used to connect to steam. """ Greenlet.__init__(self) # High Level properties self.credential = credential self.worker_manager = worker_manager self.app = self.worker_manager.app self.client = SteamClient() self.dota = dota2.Dota2Client(self.client) # Game settings self.id = id self.name = name self.password = password self.team1 = team1 self.team2 = team2 self.team1_ids = team1_ids self.team2_ids = team2_ids self.admins = admins self.casters = casters self.lobby_options = { 'game_name': self.name, 'pass_key': self.password, 'game_mode': dota2.enums.DOTA_GameMode.DOTA_GAMEMODE_CM, 'server_region': int(dota2.enums.EServerRegion.Europe), 'fill_with_bots': False, 'allow_spectating': True, #'allow_cheats': False, 'allow_cheats': True, 'allchat': False, 'dota_tv_delay': 2, 'pause_setting': 1, #'leagueid': 4947 # FTV LEAGUE SEASON 1 #'leagueid': 9674 # FTV LEAGUE SEASON 2 } # Choices self.team_choosing_first = team_choosing_first self.team_choosing_now = team_choosing_first self.team_choices = [None, None] self.team_choices_possibilities = ['!radiant', '!dire', '!fp', '!sp'] self.team_inverted = False # State machine variables self.machine_state = DotaBotState.STARTING self.lobby_status = {} """ self.lobby_channel_id = None self.invite_timer = None self.missing_players = None self.missing_players_count = None self.wrong_team_players = None self.wrong_team_players_count = None """ # Prepare all event handlers # - Steam client events # - Dota client events # - Bot events self.client.on('connected', self.steam_connected) self.client.on('logged_on', self.steam_logged) self.client.on('disconnected', self.steam_disconnected) self.dota.on('ready', self.dota_ready) self.dota.on('notready', self.closed_dota) self.dota.on(dota2.features.Lobby.EVENT_LOBBY_NEW, self.game_hosted) self.dota.on(dota2.features.Lobby.EVENT_LOBBY_CHANGED, self.game_update) self.dota.channels.on( dota2.features.chat.ChannelManager.EVENT_JOINED_CHANNEL, self.channel_join) self.dota.channels.on(dota2.features.chat.ChannelManager.EVENT_MESSAGE, self.channel_message) def _run(self): """Start the main loop of the thread, connecting to Steam, waiting for the job to finish to close the bot.""" self.print_info('Connecting to Steam...') self.client.connect(retry=None) # Try connecting with infinite retries while self.machine_state != DotaBotState.SETUP_GAME: sleep(5) self.initialize_lobby() sleep(10) # Wait for setup remaining_time = 1800 # Attente 30 min au max # P1: Wait for people to join for 25 minutes, P2 if slots are filled (X=min remaining ) self.print_info('Waiting for players.') team_names, missing_players = self.check_teams() while (remaining_time > 300 and (team_names[0] is False or team_names[1] is False or missing_players[0] != 0 or missing_players[1] != 0)): self.display_status(remaining_time, missing_players, team_names) sleep(30) remaining_time -= 30 team_names, missing_players = self.check_teams() # P2: Give 1min for each teams to pick sides/picks self.print_info('Choice side/order.') self.machine_state = DotaBotState.PICKING_SIDE_ORDER msg = '{0} Choix du side/ordre, team {1} choisit entre {2}.'.format( self.remaining_time_to_string(remaining_time), self.team_choosing_now, ', '.join(self.team_choices_possibilities)) self.dota.channels.lobby.send(msg) team_choice_remaining_time = [60, 60] while team_choice_remaining_time[self.team_choosing_now - 1]: sleep(1) remaining_time -= 1 team_choice_remaining_time[self.team_choosing_now - 1] -= 1 if self.team_choices[self.team_choosing_now - 1] is not None: break if self.team_choices[self.team_choosing_now - 1] is None: # RANDOM self.team_choices[self.team_choosing_now - 1] = random.choice( self.team_choices_possibilities)[1:] msg = '{0} Team {1} a random {2}.'.format( self.remaining_time_to_string(remaining_time), self.team_choosing_now, self.team_choices[self.team_choosing_now - 1]) else: # Choice msg = '{0} Team {1} a choisi {2}.'.format( self.remaining_time_to_string(remaining_time), self.team_choosing_now, self.team_choices[self.team_choosing_now - 1]) self.team_choices_possibilities.remove('!{0}'.format( self.team_choices[self.team_choosing_now - 1])) complementary = { 'fp': '!sp', 'sp': '!fp', 'radiant': '!dire', 'dire': '!radiant' } self.team_choices_possibilities.remove( complementary[self.team_choices[self.team_choosing_now - 1]]) self.team_choosing_now = (self.team_choosing_now % 2) + 1 msg = '{0} Team {1} choisit entre {2}.'.format( msg, self.team_choosing_now, ', '.join(self.team_choices_possibilities)) self.dota.channels.lobby.send(msg) while team_choice_remaining_time[self.team_choosing_now - 1]: sleep(1) remaining_time -= 1 team_choice_remaining_time[self.team_choosing_now - 1] -= 1 if self.team_choices[self.team_choosing_now - 1] is not None: break if self.team_choices[self.team_choosing_now - 1] is None: # RANDOM self.team_choices[self.team_choosing_now - 1] = random.choice( self.team_choices_possibilities)[1:] msg = '{0} Team {1} a random {2}.'.format( self.remaining_time_to_string(remaining_time), self.team_choosing_now, self.team_choices[self.team_choosing_now - 1]) else: # Choice msg = '{0} Team {1} a choisi {2}. Lancement dès que le lobby est complet.'.format( self.remaining_time_to_string(remaining_time), self.team_choosing_now, self.team_choices[self.team_choosing_now - 1]) self.dota.channels.lobby.send(msg) # Config if self.team_choices[0] == 'dire' or self.team_choices[1] == 'radiant': self.team_inverted = True self.dota.flip_lobby_teams() if ((self.team_choices[0] == 'fp' and self.team_choices[1] == 'dire') or (self.team_choices[0] == 'sp' and self.team_choices[1] == 'radiant') or (self.team_choices[0] == 'radiant' and self.team_choices[1] == 'sp') or (self.team_choices[0] == 'dire' and self.team_choices[1] == 'fp')): self.lobby_options['cm_pick'] = DOTA_CM_PICK.DOTA_CM_GOOD_GUYS else: self.lobby_options['cm_pick'] = DOTA_CM_PICK.DOTA_CM_BAD_GUYS self.dota.config_practice_lobby(options=self.lobby_options) self.machine_state = DotaBotState.WAITING_FOR_READY # Resync time to modulo 30 sleep(remaining_time % 30) remaining_time -= remaining_time % 30 # P3: Give min(1, 30-X-2) min for teams to get into slots, starts when both teams ready team_names, missing_players = self.check_teams() while (remaining_time > 0 and (team_names[0] is False or team_names[1] is False or missing_players[0] != 0 or missing_players[1] != 0)): self.display_status(remaining_time, missing_players, team_names) sleep(30) remaining_time -= 30 team_names, missing_players = self.check_teams() # Cancel lobby test if remaining_time <= 0: self.print_info('Lobby incomplet, annulation de la game.') self.dota.channels.lobby.send('Joueurs manquants, lobby annulé.') with self.app.app_context(): game = db.session().query(Game).filter( Game.id == self.id).one_or_none() if game is not None: game.status = GameStatus.CANCELLED db.session().commit() sleep(15) self.end_bot() # Start and retry self.dota.channels.lobby.send('Démarrage de la partie...') self.machine_state = DotaBotState.LOADING_GAME self.dota.launch_practice_lobby() while self.machine_state == DotaBotState.LOADING_GAME: sleep(5) if self.lobby_status.state == 0: self.print_info( 'Erreur lors du chargement, annulation de la game.') self.dota.channels.lobby.send( 'Impossible de charger la partie, game annulée. Contactez un admin.' ) with self.app.app_context(): game = db.session().query(Game).filter( Game.id == self.id).one_or_none() if game is not None: game.status = GameStatus.CANCELLED db.session().commit() sleep(15) self.end_bot() # IN GAME WAIT self.print_info('Game in progress...') with self.app.app_context(): game = db.session().query(Game).filter( Game.id == self.id).one_or_none() if game is not None: game.status = GameStatus.GAME_IN_PROGRESS db.session().commit() while self.lobby_status.state != 3: sleep(30) # Game over self.machine_state = DotaBotState.GAME_FINISHED self.print_info('Game completed.') with self.app.app_context(): game = db.session().query(Game).filter( Game.id == self.id).one_or_none() if game is not None: game.status = GameStatus.COMPLETED game.valve_id = self.lobby_status.match_id if ((self.lobby_status.match_outcome == 2 and not self.team_inverted) or (self.lobby_status.match_outcome == 3 and self.team_inverted)): game.winner = 1 else: game.winner = 2 db.session().commit() self.end_bot() def end_bot(self): """End the life of the bot.""" self.print_info('Bot work over.') self.machine_state = DotaBotState.FINISHED self.dota.destroy_lobby() sleep(1) self.client.disconnect() self.worker_manager.bot_end(self.credential) self.kill() # Helpers def print_info(self, trace): """Wrapper of `logging.info` with bot name prefix. Args: trace: String to output as INFO. """ logging.info('%s: %s', self.credential.login, trace) def print_error(self, trace): """Wrapper of `logging.error` with bot name prefix. Args: trace: String to output as ERROR. """ logging.error("%s: %s", self.credential.login, trace) # Callback of Steam and Dota clients def steam_connected(self): """Callback fired when the bot is connected to Steam, login user.""" self.print_info('Connected to Steam.') self.client.login(self.credential.login, self.credential.password) def steam_logged(self): """Callback fired when the bot is logged into Steam, starting Dota.""" self.print_info('Logged to Steam.') self.dota.launch() def steam_disconnected(self): """Callback fired when the bot is disconnected from Steam""" self.print_info('Disconnected from Steam.') def dota_ready(self): """Callback fired when the Dota application is ready, resume the job processing.""" self.print_info('Dota application is ready.') sleep( 10 ) # Safety to leave already existing lobby if bot is lost in the matrix self.host_game() def closed_dota(self): """Callback fired when the Dota application is closed.""" self.print_info('Dota application is closed.') # Messaging events def channel_join(self, channel_info): pass #self.print_info('Channel join! {0}'.format(channel_info)) """ if channel_info.channel_type != dota2.enums.DOTAChatChannelType_t.DOTAChannelType_Lobby: self.dota.leave_channel(channel_info.channel_id) else: if self.game_status is not None: if channel_info.channel_name == 'Lobby_{0}'.format(self.game_status.lobby_id): self.lobby_channel_id = channel_info.channel_id""" def channel_message(self, channel, message): if message.text[0] != '!': return command = message.text[1:].strip().split(' ') if len(command) == 0: return message_steam_id = SteamID(message.account_id).as_64 # Feature commands if command[0] == 'commands': self.dota.channels.lobby.send( 'Commandes: !cocaster, !destroy, !standin') elif command[0] == 'philaeux': self.dota.channels.lobby.send( 'Respectez mon créateur ou je vous def lose.') elif command[0] == 'cocaster': if message_steam_id not in self.admins and message_steam_id not in self.casters: self.dota.channels.lobby.send( 'Seuls les casters et admins peuvent ajouter un cocaster.') return if len(command) != 2: self.dota.channels.lobby.send( '!cocaster X où X est le steamID (64bits) du cocaster.') return if not command[1].isdigit(): self.dota.channels.lobby.send( 'SteamID (64bits) invalide dans la commande !cocaster.') else: self.casters.append(int(command[1])) self.dota.channels.lobby.send('Cocaster {0} ajouté.'.format( command[1])) elif command[0] == 'standin': if message_steam_id not in self.admins: self.dota.channels.lobby.send( 'Seuls les admins peuvent ajouter un standin.') return if len(command) != 3: self.dota.channels.lobby.send( "!standin X Y où X est le steamID (64bits) du standin et Y l'équipe (1 ou 2)." ) return if not command[1].isdigit(): self.dota.channels.lobby.send( 'SteamID (64bits) invalide dans la commande !standin.') return if command[2] not in ['1', '2']: self.dota.channels.lobby.send( 'Équipe (1 ou 2) invalide dans la commande !standin.') else: if command[2] == '1': self.team1_ids.append(int(command[1])) else: self.team2_ids.append(int(command[1])) self.dota.channels.lobby.send( "Standin {0} ajouté à l'équipe {1}.".format( command[1], command[2])) elif command[0] == 'destroy': if message_steam_id not in self.admins: self.dota.channels.lobby.send( 'Seuls les admins peuvent détruirent le lobby.') else: self.dota.channels.lobby.send('Lobby annulé par un admin.') with self.app.app_context(): game = db.session().query(Game).filter( Game.id == self.id).one_or_none() if game is not None: game.status = GameStatus.CANCELLED db.session().commit() self.end_bot() # Main Loop commands elif command[0] == 'fp' or command[0] == 'sp' or command[ 0] == 'radiant' or command[0] == 'dire': if '!{0}'.format(command[0]) in self.team_choices_possibilities: if self.machine_state == DotaBotState.PICKING_SIDE_ORDER: compare = self.team1_ids if self.team_choosing_now == 1 else self.team2_ids if message_steam_id in compare: self.team_choices[self.team_choosing_now - 1] = command[0] # Hosting events def host_game(self): """Start the processing of the job with the appropriate handler.""" self.print_info('Hosting game {0} with password {1}'.format( self.name, self.password)) self.machine_state = DotaBotState.HOSTING_GAME self.dota.create_practice_lobby(password=self.password) def game_hosted(self, message): """Callback fired when the Dota bot enters a lobby.""" if self.machine_state != DotaBotState.HOSTING_GAME: self.dota.destroy_lobby() self.dota.leave_practice_lobby( ) # Sometimes the bot get back to an old lobby at startup return self.lobby_status = message self.machine_state = DotaBotState.SETUP_GAME def game_update(self, message): """Callback fired when the game lobby change, update local information.""" self.lobby_status = message if self.machine_state == DotaBotState.LOADING_GAME: if self.lobby_status.state == 0: self.machine_state = DotaBotState.RETRY_WAITING_FOR_READY elif self.lobby_status.state == 2: if self.lobby_status.game_state not in [0, 1]: self.machine_state = DotaBotState.GAME_IN_PROGRESS # Kick players not authorized for member in message.members: if member.id == self.dota.steam_id: continue if (member.id not in self.team1_ids and member.id not in self.team2_ids and member.id not in self.admins and member.id not in self.casters): self.dota.practice_lobby_kick(SteamID(member.id).as_32) if ((member.team == DOTA_GC_TEAM.SPECTATOR) or (member.team == DOTA_GC_TEAM.BROADCASTER and not (member.id in self.admins or member.id in self.casters))): self.dota.practice_lobby_kick_from_team( SteamID(member.id).as_32) else: if self.team_inverted: if ((member.team == DOTA_GC_TEAM.BAD_GUYS and member.id not in self.team1_ids) or (member.team == DOTA_GC_TEAM.GOOD_GUYS and member.id not in self.team2_ids)): self.dota.practice_lobby_kick_from_team( SteamID(member.id).as_32) else: if ((member.team == DOTA_GC_TEAM.GOOD_GUYS and member.id not in self.team1_ids) or (member.team == DOTA_GC_TEAM.BAD_GUYS and member.id not in self.team2_ids)): self.dota.practice_lobby_kick_from_team( SteamID(member.id).as_32) def initialize_lobby(self): """Setup the game lobby with the good options, and change status in database.""" self.print_info('Game hosted, setup.') self.dota.channels.join_lobby_channel() self.dota.join_practice_lobby_team() self.dota.config_practice_lobby(options=self.lobby_options) with self.app.app_context(): game = db.session().query(Game).filter( Game.id == self.id).one_or_none() if game is not None: game.status = GameStatus.WAITING_FOR_PLAYERS db.session().commit() self.machine_state = DotaBotState.WAITING_FOR_PLAYERS def check_teams(self): """Check team players and team names.""" missing_players = [ min(5, len(self.team1_ids)), min(5, len(self.team2_ids)) ] team_names = [False, False] for member in self.lobby_status.members: if member.team == DOTA_GC_TEAM.GOOD_GUYS: missing_players[0] = missing_players[0] - 1 elif member.team == DOTA_GC_TEAM.BAD_GUYS: missing_players[1] = missing_players[1] - 1 i = 0 if self.team_inverted: compare = [self.team2, self.team1] else: compare = [self.team1, self.team2] for team_detail in self.lobby_status.team_details: team_names[i] = team_detail.team_id == compare[i] i = i + 1 return team_names, missing_players def display_status(self, remaining_time, missing_players, team_names): """Display status in chat.""" msg = self.remaining_time_to_string(remaining_time) if not team_names[0]: msg = msg + " Le Radiant n'a pas la bonne team." if missing_players[0] == 1: msg = msg + " 1 joueur Radiant manquant." elif missing_players[0] > 1: msg = msg + " {0} joueurs Radiant manquants.".format( missing_players[0]) if not team_names[1]: msg = msg + " Le Dire n'a pas la bonne team." if missing_players[1] == 1: msg = msg + " 1 joueur Dire manquant." elif missing_players[1] > 1: msg = msg + " {0} joueurs Dire manquants.".format( missing_players[1]) self.dota.channels.lobby.send(msg) @staticmethod def remaining_time_to_string(remaining_time): return '{0}m{1:02d}s -'.format(remaining_time // 60, remaining_time % 60)