def set_game_state(state): global sm if sm is None: sm = SetupManager() sm.connect_to_game() game_state = dict_to_game_state(state) sm.game_interface.set_game_state(game_state)
def run_challenge(match_config: MatchConfig, challenge: dict, upgrades: dict) -> Tuple[bool, dict]: """Launch the game and keep track of the state""" setup_manager = SetupManager() setup_manager.early_start_seconds = 5 setup_manager.connect_to_game() setup_manager.load_match_config(match_config) setup_manager.launch_early_start_bot_processes() setup_manager.start_match() setup_manager.launch_bot_processes() game_results = None try: game_results = manage_game_state(challenge, upgrades, setup_manager) except: # no matter what happens we gotta continue traceback.print_exc() print("Something failed with the game. Will proceed with shutdown") # need to make failure apparent to user setup_failure_freeplay(setup_manager, "The game failed to continue") return False, {} setup_manager.shut_down() return game_results
def setup_match(self): # Set up RLBot.cfg framework_config = create_bot_config_layout() config_location = os.path.join(os.path.dirname(__file__), 'rlbot.cfg') framework_config.parse_file(config_location, max_index=MAX_PLAYERS) match_config = parse_match_config(framework_config, config_location, {}, {}) match_config.game_map = self.choreo_obj.map_name # The three blocks of code below are basically identical. # TODO Make them into a function? # Gets appearance list from choreo. appearances = self.choreo_obj.get_appearances(self.min_bots) # Checks that it is the correct length. if len(appearances) != self.min_bots: print('[RLBotChoreography]: Number of appearances does not match number of bots.') print('[RLBotChoreography]: Using default appearances.') appearances = ['default.cfg'] * self.min_bots # Gets teams list from choreo. teams = self.choreo_obj.get_teams(self.min_bots) # Checks that it is the correct length. if len(teams) != self.min_bots: print('[RLBotChoreography]: Number of teams does not match number of bots.') print('[RLBotChoreography]: Putting all on blue.') teams = [0] * self.min_bots # Gets names list from choreo. names = self.choreo_obj.get_names(self.min_bots) # Checks that it is the correct length. if len(names) != self.min_bots: print('[RLBotChoreography]: Number of names does not match number of bots.') print('[RLBotChoreography]: Using bot indices as names.') names = range(self.min_bots) # Loads appearances. looks_configs = { idx: create_looks_configurations().parse_file( os.path.abspath('./ChoreographyHive/appearances/' + file_name)) for idx, file_name in enumerate(appearances) } # rlbot.cfg specifies only one bot, # so we have to copy each and assign correct appearance. player_config = match_config.player_configs[0] match_config.player_configs.clear() for i in range(self.min_bots): copied = PlayerConfig() copied.name = names[i] copied.team = teams[i] copied.bot = player_config.bot copied.rlbot_controlled = player_config.rlbot_controlled copied.config_path = player_config.config_path copied.loadout_config = load_bot_appearance(looks_configs[i], copied.team) match_config.player_configs.append(copied) manager = SetupManager() manager.load_match_config(match_config, {}) manager.connect_to_game() manager.start_match()
def fetch_game_tick_packet() -> GameTickPacket: global sm if sm is None: sm = SetupManager() sm.connect_to_game() game_tick_packet = GameTickPacket() sm.game_interface.update_live_data_packet(game_tick_packet) return game_tick_packet
def main(): print("starting") check_python_version() manager = SetupManager() manager.connect_to_game() manager.launch_quick_chat_manager() manager.infinite_loop() # Runs forever until interrupted
def run_tests(my_queue: Queue): """Runs the tests.""" check_python_version() manager = SetupManager() has_started = False for config in TESTS: if len(TESTS[config]) == 0: continue # Start a match. directory = os.path.abspath(os.path.dirname(__file__)) config_location = os.path.join(directory, config, "match.cfg") manager.load_config(config_location=config_location) manager.connect_to_game() manager.launch_early_start_bot_processes() manager.start_match() manager.launch_bot_processes() if not has_started: # Let other thread know that game has been launched. my_queue.put(Message.READY) has_started = True # Load first test for this config. test_num = 0 my_queue.put((get_game_state( os.path.join(directory, config, TESTS[config][test_num])), None)) while not manager.quit_event.is_set(): manager.try_recieve_agent_metadata() # Move onto the next test. if keyboard.is_pressed(NEXT_KEY): test_num += 1 # If we have exceeded the number of tests in this config, # break and go to the next config. if len(TESTS[config]) <= test_num: break # Loads the next test. my_queue.put((get_game_state( os.path.join(directory, config, TESTS[config][test_num])), None)) # Prevent accidental multiple key presses. time.sleep(1) # Kill all bot processes. manager.shut_down(kill_all_pids=True) my_queue.put((None, Message.DONE)) print("Thread 1 closing.") exit()
def main(): print("starting") check_python_version() manager = SetupManager() manager.load_config() manager.connect_to_game() manager.launch_early_start_bot_processes() manager.start_match() manager.launch_bot_processes() manager.infinite_loop() # Runs forever until interrupted
def main(gym=False): print(f"Starting Custom runner with gym:{gym}") check_python_version() manager = SetupManager(gym) manager.connect_to_game() manager.load_config() manager.launch_ball_prediction() manager.launch_quick_chat_manager() manager.launch_bot_processes() manager.start_match() return manager
def start_match_helper(bot_list, match_settings): print(bot_list) print(match_settings) match_config = MatchConfig() match_config.game_mode = match_settings['game_mode'] match_config.game_map = match_settings['map'] match_config.skip_replays = match_settings['skip_replays'] match_config.instant_start = match_settings['instant_start'] match_config.enable_lockstep = match_settings['enable_lockstep'] match_config.enable_rendering = match_settings['enable_rendering'] match_config.enable_state_setting = match_settings['enable_state_setting'] match_config.auto_save_replay = match_settings['auto_save_replay'] match_config.existing_match_behavior = match_settings['match_behavior'] match_config.mutators = MutatorConfig() mutators = match_settings['mutators'] match_config.mutators.match_length = mutators['match_length'] match_config.mutators.max_score = mutators['max_score'] match_config.mutators.overtime = mutators['overtime'] match_config.mutators.series_length = mutators['series_length'] match_config.mutators.game_speed = mutators['game_speed'] match_config.mutators.ball_max_speed = mutators['ball_max_speed'] match_config.mutators.ball_type = mutators['ball_type'] match_config.mutators.ball_weight = mutators['ball_weight'] match_config.mutators.ball_size = mutators['ball_size'] match_config.mutators.ball_bounciness = mutators['ball_bounciness'] match_config.mutators.boost_amount = mutators['boost_amount'] match_config.mutators.rumble = mutators['rumble'] match_config.mutators.boost_strength = mutators['boost_strength'] match_config.mutators.gravity = mutators['gravity'] match_config.mutators.demolish = mutators['demolish'] match_config.mutators.respawn_time = mutators['respawn_time'] human_index_tracker = IncrementingInteger(0) match_config.player_configs = [create_player_config(bot, human_index_tracker) for bot in bot_list] match_config.script_configs = [create_script_config(script) for script in match_settings['scripts']] global sm if sm is not None: try: sm.shut_down() except Exception as e: print(e) sm = SetupManager() sm.early_start_seconds = 5 sm.connect_to_game() sm.load_match_config(match_config) sm.launch_early_start_bot_processes() sm.start_match() sm.launch_bot_processes()
def record_atba(): raw_config_parser = configparser.RawConfigParser() raw_config_parser.read(RLBOT_CONFIG_FILE) framework_config = create_bot_config_layout() framework_config.parse_file(raw_config_parser, max_index=10) manager = SetupManager() manager.connect_to_game() manager.load_config(framework_config=framework_config, config_location=RLBOT_CONFIG_FILE) manager.launch_ball_prediction() manager.launch_quick_chat_manager() manager.launch_bot_processes() manager.start_match() manager.infinite_loop() # Runs terminated by timeout in other thread.
def start_match_helper(bot_list, match_settings): print(bot_list) print(match_settings) num_participants = len(bot_list) match_config = MatchConfig() match_config.num_players = num_participants match_config.game_mode = match_settings['game_mode'] match_config.game_map = match_settings['map'] match_config.mutators = MutatorConfig() mutators = match_settings['mutators'] match_config.mutators.match_length = mutators['match_length'] match_config.mutators.max_score = mutators['max_score'] match_config.mutators.overtime = mutators['overtime'] match_config.mutators.series_length = mutators['series_length'] match_config.mutators.game_speed = mutators['game_speed'] match_config.mutators.ball_max_speed = mutators['ball_max_speed'] match_config.mutators.ball_type = mutators['ball_type'] match_config.mutators.ball_weight = mutators['ball_weight'] match_config.mutators.ball_size = mutators['ball_size'] match_config.mutators.ball_bounciness = mutators['ball_bounciness'] match_config.mutators.boost_amount = mutators['boost_amount'] match_config.mutators.rumble = mutators['rumble'] match_config.mutators.boost_strength = mutators['boost_strength'] match_config.mutators.gravity = mutators['gravity'] match_config.mutators.demolish = mutators['demolish'] match_config.mutators.respawn_time = mutators['respawn_time'] human_index_tracker = IncrementingInteger(0) match_config.player_configs = [ create_player_config(bot, human_index_tracker) for bot in bot_list ] global sm if sm is not None: try: sm.shut_down() except Exception as e: print(e) sm = SetupManager() sm.connect_to_game() sm.load_match_config(match_config) sm.launch_ball_prediction() sm.launch_quick_chat_manager() sm.launch_bot_processes() sm.start_match()
def run_match(file_path): assert os.path.exists(file_path), f"Invalid path: {file_path}" _, ext = os.path.splitext(file_path) assert ext == ".cfg", f"Wrong file extension: '{ext}', expected '.cfg'" print(f"Using config at {file_path}") manager = SetupManager() manager.load_config(config_location=file_path) try: manager.connect_to_game() manager.launch_early_start_bot_processes() manager.start_match() manager.launch_bot_processes() except Exception: traceback.print_exc() finally: manager.shut_down(kill_all_pids=True)
def setup_match(self): # TODO This should be replaced? arguments = docopt(__doc__) bot_directory = arguments['--bot-folder'] bundles = scan_directory_for_bot_configs(bot_directory) # Set up RLBot.cfg framework_config = create_bot_config_layout() config_location = os.path.join(os.path.dirname(__file__), 'rlbot.cfg') framework_config.parse_file(config_location, max_index=MAX_PLAYERS) match_config = parse_match_config(framework_config, config_location, {}, {}) looks_configs = { idx: bundle.get_looks_config() for idx, bundle in enumerate(bundles) } names = [bundle.name for bundle in bundles] player_config = match_config.player_configs[0] match_config.player_configs.clear() for i in range(max(len(bundles), self.min_bots)): copied = PlayerConfig() copied.bot = player_config.bot copied.name = player_config.name copied.rlbot_controlled = player_config.rlbot_controlled copied.config_path = player_config.config_path copied.team = player_config.team if i % 2 == 0 else not player_config.team if i < len(bundles): copied.name = names[i] # If you want to override bot appearances to get a certain visual effect, e.g. with # specific boost colors, this is a good place to do it. copied.loadout_config = load_bot_appearance( looks_configs[i], 0) match_config.player_configs.append(copied) manager = SetupManager() manager.load_match_config(match_config, {}) manager.connect_to_game( RocketLeagueLauncherPreference( RocketLeagueLauncherPreference.STEAM, False)) manager.start_match()
def __init__(self): match_config = MatchConfig() match_config.game_mode = 'Soccer' match_config.game_map = 'Mannfield_Night' match_config.existing_match_behavior = 'Continue And Spawn' match_config.mutators = MutatorConfig() match_config.mutators.match_length = 'Unlimited' # there needs to be at least one bot for the match to start bot_config = PlayerConfig() bot_config.name = "Dummy" bot_config.team = 0 bot_config.bot = True bot_config.rlbot_controlled = True match_config.player_configs = [bot_config] sm = SetupManager() sm.connect_to_game() sm.load_match_config(match_config) sm.start_match() self.game_interface: GameInterface = sm.game_interface
class TestSpawner: def __init__(self, python_file: Path, standalone_bot_config: StandaloneBotConfig, bundle: BotConfigBundle): self.python_file = python_file self.standalone_bot_config = standalone_bot_config self.bundle = bundle self.player_config: PlayerConfig = None self.setup_manager: SetupManager = None self.spawn_id = self.create_spawn_id() self.player_index = standalone_bot_config.player_index or 0 self.team = standalone_bot_config.team or 0 self.name = self.get_bot_name() def get_bot_name(self) -> str: if self.bundle is not None: return self.bundle.name if self.standalone_bot_config.name is not None: print( f'Spawning your bot with the name {self.standalone_bot_config.name} because no config path was provided!' ) return self.standalone_bot_config.name print( f'Spawning your bot with the name {self.python_file.name} because no config path was provided!' ) return self.python_file.name def create_spawn_id(self): """ We want a spawn id unique to the python file which will be stable across re-runs. """ hash = hashlib.sha1(str(self.python_file).encode('utf-8')) number_form = int(hash.hexdigest(), 16) return number_form % FLATBUFFER_MAX_INT def create_player_config(self, config_path: str) -> PlayerConfig: player_config = PlayerConfig() player_config.bot = True player_config.rlbot_controlled = True player_config.bot_skill = 1 player_config.human_index = 0 player_config.name = self.name player_config.team = 0 player_config.config_path = config_path player_config.spawn_id = self.spawn_id return player_config def build_match_config(self, player_config_path: str): if self.player_config is None: self.player_config = self.create_player_config(player_config_path) match_config = MatchConfig() match_config.player_configs = [self.player_config] match_config.game_mode = 'Soccer' match_config.game_map = 'DFHStadium' match_config.existing_match_behavior = 'Continue And Spawn' match_config.mutators = MutatorConfig() match_config.enable_state_setting = True match_config.enable_rendering = True return match_config def spawn_bot(self): config_path = None if self.bundle is not None: config_path = self.bundle.config_path match_config = self.build_match_config(config_path) if self.setup_manager is None: self.setup_manager = SetupManager() rlbot_gateway_process, _ = gateway_util.find_existing_process() if rlbot_gateway_process is None: # RLBot.exe is not running yet, we should use the Restart behavior. # That avoids a situation where dead cars start piling up when # RLBot.exe gets killed and re-launched over and over and lacks context # to clean up previous cars. match_config.existing_match_behavior = 'Restart' self.setup_manager.connect_to_game() self.setup_manager.load_match_config(match_config) self.setup_manager.start_match()
def spawn_car_in_showroom(loadout_config: LoadoutConfig, team: int, showcase_type: str, map_name: str, launcher_prefs: RocketLeagueLauncherPreference): match_config = MatchConfig() match_config.game_mode = 'Soccer' match_config.game_map = map_name match_config.instant_start = True match_config.existing_match_behavior = 'Continue And Spawn' match_config.networking_role = NetworkingRole.none match_config.enable_state_setting = True match_config.skip_replays = True bot_config = PlayerConfig() bot_config.bot = True bot_config.rlbot_controlled = True bot_config.team = team bot_config.name = "Showroom" bot_config.loadout_config = loadout_config match_config.player_configs = [bot_config] match_config.mutators = MutatorConfig() match_config.mutators.boost_amount = 'Unlimited' match_config.mutators.match_length = 'Unlimited' global sm if sm is None: sm = SetupManager() sm.connect_to_game(launcher_preference=launcher_prefs) sm.load_match_config(match_config) sm.start_match() game_state = GameState( cars={0: CarState(physics=Physics( location=Vector3(0, 0, 20), velocity=Vector3(0, 0, 0), angular_velocity=Vector3(0, 0, 0), rotation=Rotator(0, 0, 0) ))}, ball=BallState(physics=Physics( location=Vector3(0, 0, -100), velocity=Vector3(0, 0, 0), angular_velocity=Vector3(0, 0, 0) )) ) player_input = PlayerInput() team_sign = -1 if team == 0 else 1 if showcase_type == "boost": player_input.boost = True player_input.steer = 1 game_state.cars[0].physics.location.y = -1140 game_state.cars[0].physics.velocity.x = 2300 game_state.cars[0].physics.angular_velocity.z = 3.5 elif showcase_type == "throttle": player_input.throttle = 1 player_input.steer = 0.56 game_state.cars[0].physics.location.y = -1140 game_state.cars[0].physics.velocity.x = 1410 game_state.cars[0].physics.angular_velocity.z = 1.5 elif showcase_type == "back-center-kickoff": game_state.cars[0].physics.location.y = 4608 * team_sign game_state.cars[0].physics.rotation.yaw = -0.5 * pi * team_sign elif showcase_type == "goal-explosion": game_state.cars[0].physics.location.y = -2000 * team_sign game_state.cars[0].physics.rotation.yaw = -0.5 * pi * team_sign game_state.cars[0].physics.velocity.y = -2300 * team_sign game_state.ball.physics.location = Vector3(0, -3500 * team_sign, 93) sm.game_interface.update_player_input(player_input, 0) sm.game_interface.set_game_state(game_state)
class PokebotTrainer(BaseScript): def __init__(self): super().__init__("Pokebot Trainer") self.action_broker = MyActionBroker(self) self.active_bots: List[ActiveBot] = [] self.available_bots: Dict[str, BotConfigBundle] = {b.name: b for b in self.get_bots()} self.available_bot_names: List[str] = list(self.available_bots.keys()) self.available_bot_names.sort() self.logger.info(f"Pokebot trainer has: {self.available_bots.keys()}") self.setup_manager = SetupManager() self.ready = False self.requested_relaunch: MatchConfig = None self.bots_pending_post_spawn_processing: List[ActiveBot] = [] def index_from_spawn_id(self, spawn_id): packet = self.game_tick_packet for n in range(0, packet.num_cars): packet_spawn_id = packet.game_cars[n].spawn_id if spawn_id == packet_spawn_id: return n return None def heartbeat_connection_attempts_to_twitch_broker(self, port): register_api_config = Configuration() register_api_config.host = f"http://127.0.0.1:{STANDARD_TWITCH_BROKER_PORT}" twitch_broker_register = RegisterApi(ApiClient(configuration=register_api_config)) while True: try: twitch_broker_register.register_action_server( ActionServerRegistration(base_url=f"http://127.0.0.1:{port}")) except MaxRetryError: self.logger.warning('Failed to register with twitch broker, will try again...') sleep(10) def get_bots(self): return scan_directory_for_bot_configs(BOT_DIRECTORY) def get_actions_currently_available(self) -> List[AvailableActions]: actions = [] for name in self.available_bot_names: blue_text = highlight_team_color(f"blue {name}", 0) actions.append(BotAction(description=f"Spawn {blue_text}", action_type=SPAWN, data={'name': name, 'team': 0})) for name in self.available_bot_names: orange_text = highlight_team_color(f"orange {name}", 1) actions.append(BotAction(description=f"Spawn {orange_text}", action_type=SPAWN, data={'name': name, 'team': 1})) return [AvailableActions(self.name, None, actions)] def process_choice(self, choice: BotAction): if not self.setup_manager.has_started: self.logger.error(f"Tried to {choice.description} before the setup manager was fully started!") return if choice.action_type == SPAWN: name = choice.data['name'] team = choice.data['team'] bundle = self.available_bots[name] names = set([ab.name for ab in self.active_bots if ab is not None]) unique_name = name[:31] count = 2 while unique_name in names: unique_name = f'{name[:27]} ({count})' # Truncate at 27 because we can have up to '(10)' appended count += 1 new_bot = ActiveBot(unique_name, team, randint(1, 2**31 - 1), self.game_tick_packet.game_info.seconds_elapsed, bundle) try: none_index = self.active_bots.index(None) self.active_bots[none_index] = new_bot except ValueError: self.active_bots.append(new_bot) self.bots_pending_post_spawn_processing.append(new_bot) self.set_pending_relaunch_config(self.active_bots) def set_pending_relaunch_config(self, active_bots: List[ActiveBot]): match_config = MatchConfig() match_config.player_configs = [player_config_from_active_bot(ab) for ab in active_bots] match_config.game_mode = 'Soccer' match_config.game_map = 'DFHStadium' match_config.existing_match_behavior = 'Continue And Spawn' match_config.mutators = MutatorConfig() self.requested_relaunch = match_config def execute_relaunch(self): match_config = self.requested_relaunch if match_config is None: return self.requested_relaunch = None self.setup_manager.load_match_config(match_config) self.setup_manager.start_match() self.setup_manager.launch_bot_processes(match_config=match_config) self.setup_manager.try_recieve_agent_metadata() self.logger.info("Done relaunching bots") if GIVE_BOOST: car_states = {} self.get_game_tick_packet() for bot in self.bots_pending_post_spawn_processing: index = self.index_from_spawn_id(bot.spawn_id) if index is not None: car_states[index] = CarState(boost_amount=100) self.set_game_state(GameState(cars=car_states)) self.bots_pending_post_spawn_processing.clear() def start(self): port = find_usable_port(9886) Thread(target=run_action_server, args=(port,), daemon=True).start() set_bot_action_broker(self.action_broker) Thread(target=self.heartbeat_connection_attempts_to_twitch_broker, args=(port,), daemon=True).start() self.setup_manager.connect_to_game() self.ready = True while True: self.get_game_tick_packet() self.setup_manager.try_recieve_agent_metadata() game_seconds = self.game_tick_packet.game_info.seconds_elapsed needs_relaunch = False next_bots: List[ActiveBot] = [] for b in self.active_bots: if b is not None and game_seconds > b.join_time + LIFESPAN: next_bots.append(None) needs_relaunch = True else: next_bots.append(b) if needs_relaunch: while len(next_bots) > 0 and next_bots[-1] is None: next_bots.pop() # Get rid of any trailing None values. self.active_bots = next_bots self.set_pending_relaunch_config(next_bots) self.execute_relaunch() sleep(0.1)
def ensure_dll_is_injected(): manager = SetupManager() manager.connect_to_game()
class RLBotQTGui(QMainWindow, Ui_MainWindow): def __init__(self): """ Creates a new QT mainwindow with the GUI """ super().__init__() self.setupUi(self) self.overall_config = None self.index_manager = IndexManager(MAX_PLAYERS) self.agents = [] self.agent_presets = {} self.bot_names_to_agent_dict = {} self.loadout_presets = {} self.current_bot = None self.overall_config_timer = None self.setup_manager = None self.match_process = None self.overall_config = None self.overall_config_path = None self.launch_in_progress = False self.car_customisation = CarCustomisationDialog(self) self.agent_customisation = AgentCustomisationDialog(self) self.mutator_customisation = MutatorEditor(self) if os.path.isfile(DEFAULT_RLBOT_CONFIG_LOCATION): self.load_overall_config(DEFAULT_RLBOT_CONFIG_LOCATION) else: self.load_off_disk_overall_config() self.init_match_settings() self.update_match_settings() self.connect_functions() self.update_bot_type_combobox() def bot_item_drop_event(self, dropped_listwidget, event): """ Switches the team for the dropped agent to the other team :param dropped_listwidget: The listwidget belonging to the new team for the agent :param event: The QDropEvent containing the source :return: """ dragged_listwidget = event.source() if dragged_listwidget is dropped_listwidget: # drops in the same widget return self.current_bot.set_team(0 if dropped_listwidget == self.blue_listwidget else 1) self.update_teams_listwidgets() def fixed_indices(self): """ Agents in the GUI might not have following overall indices, thereby a file saved through the GUI would cause other bots to start than the GUI when ran :return: CustomConfig instance, copy of the overall config which has the indices sorted out """ config = self.overall_config.copy() used_indices = sorted(self.index_manager.numbers) not_used_indices = [e for e in range(MAX_PLAYERS) if e not in used_indices] order = used_indices + not_used_indices header = config[PARTICIPANT_CONFIGURATION_HEADER] for name, config_value in header.values.items(): old_values = list(config_value.value) for i in range(MAX_PLAYERS): config_value.set_value(old_values[order[i]], index=i) return config def run_button_pressed(self): if self.launch_in_progress: # Do nothing if we're already in the process of launching a configuration. # Attempting to run again when we're in this state can result in duplicate processes. # TODO: Add a mutex around this variable here for safety. return self.launch_in_progress = True if self.setup_manager is not None: self.setup_manager.shut_down(time_limit=5, kill_all_pids=False) # Leave any external processes alive, e.g. Java or C#, since it can # be useful to keep them around. The user can kill them with the # Kill Bots button instead. self.match_process = threading.Thread(target=self.start_match) self.match_process.start() def start_match(self): """ Starts a match with the current configuration :return: """ agent_configs_dict = {} loadout_configs_dict = {} for agent in self.agents: i, agent_config, loadout_config = agent.get_configs() agent_configs_dict[i] = agent_config loadout_configs_dict[i] = loadout_config agent_configs = {} loadout_configs = {} index = 0 for i in range(MAX_PLAYERS): if i in agent_configs_dict: agent_configs[index] = agent_configs_dict[i] loadout_configs[index] = loadout_configs_dict[i] index += 1 self.setup_manager = SetupManager() self.setup_manager.load_config(self.overall_config, self.overall_config_path, agent_configs, loadout_configs) self.setup_manager.connect_to_game() self.setup_manager.launch_ball_prediction() self.setup_manager.launch_quick_chat_manager() self.setup_manager.launch_bot_processes() self.setup_manager.start_match() self.launch_in_progress = False self.setup_manager.infinite_loop() def connect_functions(self): """ Connects all events to the functions which should be called :return: """ # Lambda is sometimes used to prevent passing the event parameter. self.cfg_load_pushbutton.clicked.connect(lambda: self.load_overall_config()) self.cfg_save_pushbutton.clicked.connect(lambda: self.save_overall_config()) self.blue_listwidget.itemSelectionChanged.connect(self.load_selected_bot) self.orange_listwidget.itemSelectionChanged.connect(self.load_selected_bot) self.blue_listwidget.dropEvent = lambda event: self.bot_item_drop_event(self.blue_listwidget, event) self.orange_listwidget.dropEvent = lambda event: self.bot_item_drop_event(self.orange_listwidget, event) self.blue_name_lineedit.editingFinished.connect(self.team_settings_edit_event) self.orange_name_lineedit.editingFinished.connect(self.team_settings_edit_event) self.blue_color_spinbox.valueChanged.connect(self.team_settings_edit_event) self.orange_color_spinbox.valueChanged.connect(self.team_settings_edit_event) self.blue_minus_toolbutton.clicked.connect(lambda e: self.remove_agent(self.current_bot)) self.orange_minus_toolbutton.clicked.connect(lambda e: self.remove_agent(self.current_bot)) self.blue_plus_toolbutton.clicked.connect(lambda e: self.add_agent_button(team_index=0)) self.orange_plus_toolbutton.clicked.connect(lambda e: self.add_agent_button(team_index=1)) for child in self.bot_config_groupbox.findChildren(QWidget): if isinstance(child, QLineEdit): child.editingFinished.connect(self.bot_config_edit_event) elif isinstance(child, QSlider): child.valueChanged.connect(self.bot_config_edit_event) elif isinstance(child, QRadioButton): child.toggled.connect(self.bot_config_edit_event) elif isinstance(child, QComboBox): child.currentTextChanged.connect(self.bot_config_edit_event) self.loadout_preset_toolbutton.clicked.connect(self.car_customisation.popup) self.agent_preset_toolbutton.clicked.connect(self.agent_customisation.popup) self.preset_load_toplevel_pushbutton.clicked.connect(self.load_preset_toplevel) for child in self.match_settings_groupbox.findChildren(QWidget): if isinstance(child, QComboBox): child.currentTextChanged.connect(self.match_settings_edit_event) elif isinstance(child, QCheckBox): child.toggled.connect(self.match_settings_edit_event) self.edit_mutators_pushbutton.clicked.connect(self.mutator_customisation.popup) self.kill_bots_pushbutton.clicked.connect(self.kill_bots) self.run_button.clicked.connect(self.run_button_pressed) def load_preset_toplevel(self): preset = self.agent_customisation.load_preset_cfg() if preset is None: return self.agent_preset_combobox.setCurrentText(preset.get_name()) loadout_preset = self.add_loadout_preset(preset.looks_path) self.car_customisation.update_presets_widgets() loadout_index = index_of_config_path_in_combobox(self.loadout_preset_combobox, loadout_preset.config_path) self.loadout_preset_combobox.setCurrentIndex(loadout_index) self.current_bot.set_loadout_preset(loadout_preset) def bot_config_edit_event(self, value=None): """ Handles the events called when editing a value regarding the bot configuration :param value: the new value to store in the config :return: """ sender = self.sender() if value is None: value = sender.text() agent = self.current_bot if sender is self.bot_type_combobox: self.update_bot_type_combobox() elif sender is self.blue_radiobutton and value: # 'and value' check to make sure that one got selected if agent.get_team() != 0: agent.set_team(0) self.update_teams_listwidgets() self.blue_listwidget.setCurrentItem(self.blue_listwidget.findItems( self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) elif sender is self.orange_radiobutton and value: if agent.get_team() != 1: agent.set_team(1) self.update_teams_listwidgets() self.orange_listwidget.setCurrentItem(self.orange_listwidget.findItems( self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) elif sender is self.ign_lineedit: if agent not in self.agents: return if not agent.get_team(): listwidget = self.blue_listwidget else: listwidget = self.orange_listwidget name = self.validate_name(value, agent) old_name = self.validate_name(agent.ingame_name, agent) row = listwidget.currentRow() del self.bot_names_to_agent_dict[old_name] agent.set_name(value) self.bot_names_to_agent_dict[name] = agent self.update_teams_listwidgets() listwidget.setCurrentRow(row) elif sender is self.loadout_preset_combobox: if self.bot_config_groupbox.isEnabled() and self.current_bot is not None: index = self.loadout_preset_combobox.currentIndex() preset = self.loadout_preset_combobox.itemData(index) self.current_bot.set_loadout_preset(preset) elif sender is self.agent_preset_combobox: if value and self.bot_config_groupbox.isEnabled() and self.current_bot is not None: preset = self.agent_preset_combobox.currentData() self.current_bot.set_agent_preset(preset) agent.set_name(agent.agent_preset.config.get(BOT_CONFIG_MODULE_HEADER, BOT_NAME_KEY)) self.ign_lineedit.setText(agent.ingame_name) if not agent.get_team(): listwidget = self.blue_listwidget else: listwidget = self.orange_listwidget row = listwidget.currentRow() self.update_teams_listwidgets() listwidget.setCurrentRow(row) loadout_index = index_of_config_path_in_combobox(self.loadout_preset_combobox, preset.looks_path) if loadout_index is not None: self.loadout_preset_combobox.setCurrentIndex(loadout_index) elif sender is self.bot_level_slider: agent.set_bot_skill(value / 100) if self.cfg_autosave_checkbutton.isChecked() and os.path.isfile(self.overall_config_path): self.save_overall_config(10) def update_bot_type_combobox(self): """ Handles selecting another bot type in the combobox, hides some frames and shows others depending on the setting Also saves the new type if there is a bot selected :return: """ bot_type = self.bot_type_combobox.currentText() if bot_type == 'RLBot': self.rlbot_frame.setHidden(False) self.extra_line.setHidden(False) self.psyonix_bot_frame.setHidden(True) self.appearance_frame.setHidden(False) self.label_3.setHidden(False) self.ign_lineedit.setHidden(False) elif bot_type == 'Psyonix': self.psyonix_bot_frame.setHidden(False) self.rlbot_frame.setHidden(True) self.extra_line.setHidden(False) self.appearance_frame.setHidden(False) self.label_3.setHidden(False) self.ign_lineedit.setHidden(False) elif bot_type == 'Human': self.psyonix_bot_frame.setHidden(True) self.rlbot_frame.setHidden(True) self.extra_line.setHidden(True) self.appearance_frame.setHidden(False) self.label_3.setHidden(True) self.ign_lineedit.setHidden(True) elif bot_type == 'Party Member Bot': self.rlbot_frame.setHidden(False) self.extra_line.setHidden(False) self.psyonix_bot_frame.setHidden(True) self.appearance_frame.setHidden(False) self.label_3.setHidden(True) self.ign_lineedit.setHidden(True) if self.bot_config_groupbox.isEnabled() and self.current_bot is not None: config_type = bot_type.lower().replace(" ", "_") self.current_bot.set_participant_type(config_type) def team_settings_edit_event(self, value=None): """ Handles the events when editing a value regarding the team settings :param value: the new value to store in the config :return: """ sender = self.sender() if value is None: value = sender.text() if sender is self.blue_name_lineedit: self.overall_config.set_value(TEAM_CONFIGURATION_HEADER, "Team Blue Name", value) elif sender is self.orange_name_lineedit: self.overall_config.set_value(TEAM_CONFIGURATION_HEADER, "Team Orange Name", value) elif sender is self.blue_color_spinbox: self.overall_config.set_value(TEAM_CONFIGURATION_HEADER, "Team Blue Color", value) elif sender is self.orange_color_spinbox: self.overall_config.set_value(TEAM_CONFIGURATION_HEADER, "Team Orange Color", value) if self.cfg_autosave_checkbutton.isChecked() and os.path.isfile(self.overall_config_path): self.save_overall_config(10) def update_team_settings(self): """ Sets all team settings widgets to the value in the overall config :return: """ self.blue_name_lineedit.setText(self.overall_config.get(TEAM_CONFIGURATION_HEADER, "Team Blue Name")) self.orange_name_lineedit.setText(self.overall_config.get(TEAM_CONFIGURATION_HEADER, "Team Orange Name")) self.blue_color_spinbox.setValue(self.overall_config.getint(TEAM_CONFIGURATION_HEADER, "Team Blue Color")) self.orange_color_spinbox.setValue(self.overall_config.getint(TEAM_CONFIGURATION_HEADER, "Team Orange Color")) def load_off_disk_overall_config(self): self.cfg_autosave_checkbutton.setChecked(False) self.cfg_autosave_checkbutton.setDisabled(True) if self.overall_config is None: self.overall_config = create_bot_config_layout() GUIAgent.overall_config = self.overall_config self.overall_config.init_indices(MAX_PLAYERS) self.overall_config_path = "" self.load_agents() # self.load_bot_directory(".") self.update_teams_listwidgets() if not self.overall_config_path: self.cfg_file_path_lineedit.setStyleSheet("border: 1px solid red;") self.cfg_file_path_lineedit.setText("Please load a configuration file") self.blue_plus_toolbutton.setEnabled(False) self.orange_plus_toolbutton.setEnabled(False) self.run_button.setEnabled(False) else: self.cfg_file_path_lineedit.setText(self.overall_config_path) self.update_team_settings() self.car_customisation.update_presets_widgets() self.agent_customisation.update_presets_widgets() self.mutator_customisation.update_comboboxes() def load_overall_config(self, config_path=None): """ Loads the overall config from the config path, or asks for a path if config_path is None :param config_path: the path to load the overall_config from, if None a path is requested :return: """ if self.overall_config is None: self.overall_config = create_bot_config_layout() GUIAgent.overall_config = self.overall_config if config_path is None: config_path = QFileDialog.getOpenFileName(self, "Load Overall Config", "", "Config Files (*.cfg)")[0] if not config_path: self.statusbar.showMessage("No file selected, not loading config", 5000) return if config_path is None or not os.path.isfile(config_path): return if pathlib.Path(config_path).suffix != '.cfg': self.popup_message("This file is not a config file!", "Invalid File Extension", QMessageBox.Warning) return raw_parser = configparser.RawConfigParser() raw_parser.read(config_path, encoding='utf8') for section in ['Match Configuration', 'Participant Configuration']: if not raw_parser.has_section(section): self.popup_message(f"Config file is missing the section {section}, not loading it!", "Invalid Config File", QMessageBox.Warning) return self.overall_config_path = config_path self.overall_config.parse_file(raw_parser, MAX_PLAYERS, config_directory=os.path.dirname(self.overall_config_path)) self.load_agents() # self.load_bot_directory(".") self.update_teams_listwidgets() self.cfg_file_path_lineedit.setText(self.overall_config_path) self.cfg_file_path_lineedit.setStyleSheet("") self.run_button.setEnabled(True) self.update_team_settings() self.car_customisation.update_presets_widgets() self.agent_customisation.update_presets_widgets() self.mutator_customisation.update_comboboxes() def save_overall_config(self, time_out=0): """ Schedules a save after given time_out :param time_out: The amount of seconds it should wait before saving :return: """ def save(): if not os.path.exists(self.overall_config_path): return self.overall_config_timer.setInterval(1000) if self.remaining_save_timer > 0: self.statusbar.showMessage("Saving Overall Config in " + str(self.remaining_save_timer) + " seconds") self.remaining_save_timer -= 1 else: self.clean_overall_config_loadouts() with open(self.overall_config_path, "w", encoding='utf8') as f: f.write(str(self.fixed_indices())) self.statusbar.showMessage("Saved Overall Config to " + self.overall_config_path, 5000) self.overall_config_timer.stop() if self.overall_config_timer is None: self.overall_config_timer = QTimer() self.overall_config_timer.timeout.connect(save) save_path = self.overall_config_path if save_path is None or not os.path.isfile(save_path): save_path = QFileDialog.getSaveFileName(self, "Save Overall Config", "", "Config Files (*.cfg)")[0] if not save_path: self.statusbar.showMessage("Unable to save the configuration without a path", 5000) return self.overall_config_path = save_path self.remaining_save_timer = time_out self.overall_config_timer.start(0) def clean_overall_config_loadouts(self): """ Set all unusued loadout paths to None. This makes sure agents don't have a custom loadout when new agents are added in the gui. """ for i in range(MAX_PLAYERS): if i not in self.index_manager.numbers: self.overall_config.set_value(PARTICIPANT_CONFIGURATION_HEADER, PARTICIPANT_LOADOUT_CONFIG_KEY, "None", i) def load_selected_bot(self): """ Loads all the values belonging to the new selected agent into the bot_config_groupbox :return: """ # prevent processing from itself (clearing the other one processes this) if not self.sender().selectedItems(): return blue = True if self.sender() is self.blue_listwidget else False if blue: # deselect the other listbox self.orange_listwidget.clearSelection() else: self.blue_listwidget.clearSelection() item_name = self.sender().selectedItems()[0].text() agent = self.bot_names_to_agent_dict[item_name] if agent is None: # something went wrong if agent is None return self.current_bot = agent self.bot_config_groupbox.setEnabled(True) # Make sure that you can edit the bot # enable [-] for right listwidget if blue: self.blue_minus_toolbutton.setDisabled(False) self.orange_minus_toolbutton.setDisabled(True) else: self.orange_minus_toolbutton.setDisabled(False) self.blue_minus_toolbutton.setDisabled(True) # load the bot parameters into the edit frame agent_type = agent.get_participant_type() known_types = ['human', 'psyonix', 'rlbot', 'party_member_bot'] assert agent_type in known_types, 'Bot has unknown type: %s' % agent_type self.bot_type_combobox.setCurrentIndex(known_types.index(agent_type)) if blue: self.blue_radiobutton.setChecked(True) else: self.orange_radiobutton.setChecked(True) self.ign_lineedit.setText(agent.ingame_name) loadout_index = index_of_config_path_in_combobox( self.loadout_preset_combobox, agent.get_loadout_preset().config_path) self.loadout_preset_combobox.setCurrentIndex(loadout_index or 0) self.agent_preset_combobox.blockSignals(True) self.agent_preset_combobox.setCurrentText(agent.get_agent_preset().get_name()) self.agent_preset_combobox.blockSignals(False) self.bot_level_slider.setValue(int(agent.get_bot_skill() * 100)) def update_teams_listwidgets(self): """ Clears all items from the listwidgets and then adds everything from the self.agents list again to the right team :return: """ self.bot_names_to_agent_dict.clear() self.blue_listwidget.clear() self.orange_listwidget.clear() for agent in self.agents: name = self.validate_name(agent.ingame_name, agent) if not agent.get_team(): self.blue_listwidget.addItem(name) else: self.orange_listwidget.addItem(name) self.bot_names_to_agent_dict[name] = agent self.enable_disable_on_bot_select_deselect() # if max bot count reached: disable + button if not self.index_manager.has_free_slots(): self.blue_plus_toolbutton.setDisabled(True) self.orange_plus_toolbutton.setDisabled(True) else: self.blue_plus_toolbutton.setDisabled(False) self.orange_plus_toolbutton.setDisabled(False) def enable_disable_on_bot_select_deselect(self): """ Disables the botconfig groupbox and minus buttons when no bot is selected :return: """ if not self.blue_listwidget.selectedItems() and not self.orange_listwidget.selectedItems(): self.bot_config_groupbox.setDisabled(True) self.blue_minus_toolbutton.setDisabled(True) self.orange_minus_toolbutton.setDisabled(True) else: self.bot_config_groupbox.setDisabled(False) def validate_name(self, name, agent): """ Finds the modification of name which is not yet in the list :param name: the (new) name for the agent :param agent: the agent instance to allow the same name as the previous one if necessary :return: the best modification of name not yet in a listwidget """ if name in self.bot_names_to_agent_dict and self.bot_names_to_agent_dict[name] is not agent: i = 0 while True: if name + " (" + str(i) + ")" in self.bot_names_to_agent_dict and \ self.bot_names_to_agent_dict[name + " (" + str(i) + ")"] is not agent: i += 1 else: value = name + " (" + str(i) + ")" return value else: return name def add_agent_button(self, team_index: int): """ The method to handle the [+] button press, adds an agent to the team :param team_index: the team to set for the new agent, 0 for blue and 1 for orange :return: """ agent = self.load_agent() if agent is None: return agent.set_team(team_index) self.car_customisation.update_presets_widgets() self.agent_customisation.update_presets_widgets() self.update_teams_listwidgets() if agent.get_team() == 0: self.blue_listwidget.setCurrentItem(self.blue_listwidget.findItems( self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) else: self.orange_listwidget.setCurrentItem(self.orange_listwidget.findItems( self.validate_name(agent.get_name(), agent), QtCore.Qt.MatchExactly)[0]) def load_agents(self, config_file=None): """ Loads all agents for this team from the rlbot.cfg :param config_file: A config file that is similar to rlbot.cfg """ if config_file is not None: self.overall_config = config_file self.agents.clear() num_participants = get_num_players(self.overall_config) try: for i in range(num_participants): self.load_agent(i) except BaseException as e: raise ValueError(f"{str(e)}\nPlease check your config files! {self.overall_config_path}") def load_agent(self, overall_index: int=None): """ Loads all data for overall_index from the overall config and also loads both presets :param overall_index: the index of the targeted agent :return agent: an Agent (gui_agent) class with all loaded values """ if not self.index_manager.has_free_slots(): return None if overall_index is None: overall_index = self.index_manager.get_new_index() else: self.index_manager.use_index(overall_index) agent = self.add_agent(overall_index=overall_index) path_in_overall_config = agent.get_agent_config_path() if path_in_overall_config is None: # Fall back to the path of the first agent if there's nothing configured. path_in_overall_config = self.overall_config.getpath(PARTICIPANT_CONFIGURATION_HEADER, PARTICIPANT_CONFIG_KEY, 0) agent_preset = self.add_agent_preset(path_in_overall_config) agent.set_agent_preset(agent_preset) agent.set_name(agent_preset.config.get(BOT_CONFIG_MODULE_HEADER, BOT_NAME_KEY)) # Add the preset's loadout as a loadout own_loadout = self.add_loadout_preset(agent_preset.looks_path) # Agent has a loadout defined in overall config, load that if it is not None loadout_file_in_overall_config = self.overall_config.get(PARTICIPANT_CONFIGURATION_HEADER, PARTICIPANT_LOADOUT_CONFIG_KEY, overall_index) if loadout_file_in_overall_config is None or loadout_file_in_overall_config == "None": agent.set_loadout_preset(own_loadout) else: directory = get_python_root() file_path = loadout_file_in_overall_config loadout_file_in_overall_config = os.path.realpath(os.path.join(directory, file_path)) loadout_preset = self.add_loadout_preset(loadout_file_in_overall_config) agent.set_loadout_preset(loadout_preset) return agent def load_bot_config_bundle(self, config_bundle: BotConfigBundle): self.add_agent_preset(config_bundle.config_path) self.add_loadout_preset(config_bundle.looks_path) def load_bot_directory(self, directory): for bundle in scan_directory_for_bot_configs(directory): try: self.load_bot_config_bundle(bundle) except Exception as e: print(e) def add_agent(self, overall_index=None, team_index=None): """ Creates the agent using self.agent_class and adds it to the index manager. :param overall_index: The index of the bot in the config file if it already exists. :param team_index: The index of the team to place the agent in :return agent: an Agent (gui_agent) with either given index or a free one, returns None if there is no index given and all indices are occupied """ if overall_index is None: if not self.index_manager.has_free_slots(): return overall_index = self.index_manager.get_new_index() else: self.index_manager.use_index(overall_index) agent = GUIAgent(overall_index=overall_index) if team_index is not None: agent.set_team(team_index) self.agents.append(agent) self.overall_config.set_value(MATCH_CONFIGURATION_HEADER, PARTICIPANT_COUNT_KEY, len(self.agents)) return agent def remove_agent(self, agent: GUIAgent): """ Removes the given agent. :param agent: the agent to remove :return: """ self.index_manager.free_index(agent.overall_index) self.agents.remove(agent) self.update_teams_listwidgets() self.overall_config.set_value(MATCH_CONFIGURATION_HEADER, PARTICIPANT_COUNT_KEY, len(self.agents)) self.overall_config.set_value(PARTICIPANT_CONFIGURATION_HEADER, PARTICIPANT_LOADOUT_CONFIG_KEY, "None", agent.overall_index) if len(self.agents) == 0: return if agent.get_team() == 0: if self.blue_listwidget.count() != 0: self.blue_listwidget.setCurrentRow(self.blue_listwidget.count() - 1) else: self.orange_listwidget.setCurrentRow(self.orange_listwidget.count() - 1) else: if self.orange_listwidget.count() != 0: self.orange_listwidget.setCurrentRow(self.orange_listwidget.count() - 1) else: self.blue_listwidget.setCurrentRow(self.blue_listwidget.count() - 1) def add_loadout_preset(self, file_path: str): """ Loads a preset using file_path with all values from that path loaded :param file_path: the path to load the preset from, if invalid a default preset is returned :return preset: the loadout preset created """ if file_path is not None and os.path.isfile(file_path): name = pathlib.Path(file_path).stem else: name = "new preset" if file_path in self.loadout_presets: return self.loadout_presets[file_path] preset = LoadoutPreset(name, file_path) self.loadout_presets[preset.config_path] = preset return preset def add_agent_preset(self, file_path): """ Loads a preset using file_path with all values from that path loaded :param file_path: the path to load the preset from. We'll throw an exception if it's invalid. :return preset: the agent preset created """ if file_path is not None and os.path.isfile(file_path): name = pathlib.Path(file_path).stem else: raise FileNotFoundError(f"File path {file_path} is not found!") if name in self.agent_presets: if self.agent_presets[name].config_path == file_path: return self.agent_presets[name] else: i = 1 for preset_name in self.agent_presets: if name in preset_name: i += 1 name = f"{name} ({i})" preset = AgentPreset(name, file_path) self.agent_presets[preset.get_name()] = preset return preset def init_match_settings(self): """ Adds all items to the match settings comboboxes :return: """ self.mode_type_combobox.addItems(game_mode_types) self.map_type_combobox.addItems(map_types) def update_match_settings(self): """ Sets all match setting widgets to the values in the overall config :return: """ self.mode_type_combobox.setCurrentText(self.overall_config.get(MATCH_CONFIGURATION_HEADER, GAME_MODE)) self.map_type_combobox.setCurrentText(self.overall_config.get(MATCH_CONFIGURATION_HEADER, GAME_MAP)) self.skip_replays_checkbox.setChecked(self.overall_config.getboolean(MATCH_CONFIGURATION_HEADER, SKIP_REPLAYS)) self.instant_start_checkbox.setChecked( self.overall_config.getboolean(MATCH_CONFIGURATION_HEADER, INSTANT_START)) def match_settings_edit_event(self, value): """ Handles all edits to match settings and sets the config value to the new value :param value: the value to apply to the overall config :return: """ sender = self.sender() if sender is self.mode_type_combobox: self.overall_config.set_value(MATCH_CONFIGURATION_HEADER, GAME_MODE, value) elif sender is self.map_type_combobox: self.overall_config.set_value(MATCH_CONFIGURATION_HEADER, GAME_MAP, value) elif sender is self.skip_replays_checkbox: self.overall_config.set_value(MATCH_CONFIGURATION_HEADER, SKIP_REPLAYS, value) elif sender is self.instant_start_checkbox: self.overall_config.set_value(MATCH_CONFIGURATION_HEADER, INSTANT_START, value) elif sender is self.match_length_combobox: self.overall_config.set_value(MUTATOR_CONFIGURATION_HEADER, MUTATOR_MATCH_LENGTH, value) elif sender is self.boost_type_combobox: self.overall_config.set_value(MUTATOR_CONFIGURATION_HEADER, MUTATOR_BOOST_AMOUNT, value) if self.cfg_autosave_checkbutton.isChecked() and os.path.isfile(self.overall_config_path): self.save_overall_config(10) def popup_message(self, message: str, title: str, icon=QMessageBox.Warning): popup = QMessageBox(self) popup.setIcon(icon) popup.setWindowTitle(title) popup.setText(message) popup.setStandardButtons(QMessageBox.Ok) popup.exec_() def kill_bots(self): if self.setup_manager is not None: self.setup_manager.shut_down(time_limit=5, kill_all_pids=True) else: print("There gotta be some setup manager already") @staticmethod def main(): """ Start the GUI :return: """ app = QApplication(sys.argv) rlbot_icon = QtGui.QIcon(os.path.join(get_rlbot_directory(), 'img', 'rlbot_icon.png')) app.setWindowIcon(rlbot_icon) window = RLBotQTGui() window.show() app.exec_()