예제 #1
0
    def __init__(self, game, list_widget, run_and_stop_button, add_button, edit_button, remove_button,
                 queue_selector_buttons):
        """Class initialization.

        :param PyQt5.QtWidgets.QListWidget.QListWidget list_widget: list widget.
        :param lib.gui.helper.TwoStateButton run_and_stop_button: button for running the queue.
        :param PyQt5.QtWidgets.QPushButton.QPushButton add_button: button for adding new element to queue.
        :param PyQt5.QtWidgets.QPushButton.QPushButton edit_button: button for editing existing element in the queue.
        :param PyQt5.QtWidgets.QPushButton.QPushButton remove_button:
            button for removing existing element from the queue.
        :param list[PyQt5.QtWidgets.QPushButton.QPushButton] queue_selector_buttons:
            list of buttons for selecting different queues.
        """
        self.game = game
        self.widget = list_widget
        self.run_and_stop_button = run_and_stop_button
        self.add_button = add_button
        self.edit_button = edit_button
        self.remove_button = remove_button
        self.queue_selector_buttons = queue_selector_buttons
        self.stored_queues = [[], ] * len(self.queue_selector_buttons)  # type: List[List[QueueItem]]
        self.current_queue_index = 0
        self.setup_buttons()
        self.threads = ThreadPool()
        self.process = None
        self.add_button.clicked.connect(self.add)
        if self.widget.count() == 0:
            self.run_and_stop_button.button.setEnabled(False)
        self.select_all_item = self.add_select_all_checkbox()
        self.widget.itemChanged.connect(self.on_item_change)
        self.load_queue_from_file()
        self.change_select_all_state()
        self.stop_queue_flag = False
예제 #2
0
    def __init__(self, button, task_func, parameters):
        """Class initialization.

        :param TwoStateButton button: button that activates task.
        :param task_func: function to execute.
        :param dict parameters: function's parameters.
        """
        self.run_and_stop_button = button
        self.task_func = task_func
        self.parameters = parameters
        self.threads = ThreadPool()
        self.process = None
        self.run_and_stop_button.connect_first_state(self.execute)
        self.run_and_stop_button.connect_second_state(self.abort)
예제 #3
0
    def __init__(self, button, task_func, task_options):
        """Class initialization.

        :param lib.gui.helper.TwoStateButton button: button that activates task.
        :param function task_func: function to execute.
        :param dict task_options: function's parameters by option's key.
        """
        self.run_and_stop_button = button
        self.task_func = task_func
        self.task_options = task_options
        self.threads = ThreadPool()
        self.process = None
        for task_name, task_parameters in task_options.items():
            def add_action(parameters):
                self.run_and_stop_button.add_action(task_name, lambda: self.execute(parameters))

            add_action(task_parameters)
        self.menu = self.run_and_stop_button.button.menu()
        self.run_and_stop_button.connect_second_state(self.abort)
예제 #4
0
class SingleTask:
    """Class for working with single task of execution."""
    def __init__(self, button, task_func, parameters):
        """Class initialization.

        :param TwoStateButton button: button that activates task.
        :param task_func: function to execute.
        :param dict parameters: function's parameters.
        """
        self.run_and_stop_button = button
        self.task_func = task_func
        self.parameters = parameters
        self.threads = ThreadPool()
        self.process = None
        self.run_and_stop_button.connect_first_state(self.execute)
        self.run_and_stop_button.connect_second_state(self.abort)

    def execute(self):
        """Execute function in safe thread."""
        logger.debug(
            f"Executing single task: {self.__class__.__name__} {self.task_func.__name__}"
        )
        from lib.gui.widgets.main import MainWindow
        MainWindow.resume_recorder()
        worker = self.threads.run_thread(target=self._execute)
        worker.signals.finished.connect(
            self.run_and_stop_button.set_first_state)
        worker.signals.finished.connect(MainWindow.pause_recorder)
        self.run_and_stop_button.set_second_state()

    @safe_process_stop
    def abort(self):
        """Abort function's execution."""
        if self.process:
            logger.debug("Task was forcibly stopped.")
            self.process.terminate()
        self.threads.thread_pool.clear()
        self.run_and_stop_button.set_first_state()
        from lib.gui.widgets.main import MainWindow
        MainWindow.pause_recorder()

    def _execute(self):
        """Execute function."""
        self.process = Process(target=self.task_func, kwargs=self.parameters)
        self.process.start()
        self.process.join()
        logger.debug("Task completed.")
예제 #5
0
    def __init__(self, file_logger, settings):
        """Class initialization.

        :param logging.FileHandler file_logger: file handler of log-file.
        :param PyQt5.QtCore.QSettings.QSettings settings: GUI settings.
        """
        super().__init__()
        self.setupUi(self)
        set_default_icon(window=self)
        self.settings = settings
        self._init_window_settings()
        self.emulator_name, self.emulator_type, self.game_app_rect, self.emulator, self.game = None, None, None, None, None
        self.load_settings_from_file()
        self.game.file_logger_name = None
        if file_logger:
            self.game.file_logger_name = file_logger.baseFilename
            self.logger = QTextEditFileLogger(
                all_logger_widget=self.logger_text,
                info_logger_widget=self.logger_text_info,
                error_logger_widget=self.logger_text_error,
                log_file=file_logger.baseFilename)
        else:
            self.logger_text.setPlainText(
                "Cannot create log file because `logs` folder doesn't exists.")
        run_and_stop_button = self.create_blockable_button(
            button=self.run_queue_button)
        self.queue_list = QueueList(list_widget=self.queue_list_widget,
                                    run_and_stop_button=run_and_stop_button,
                                    add_button=self.add_queue_button,
                                    edit_button=self.edit_queue_button,
                                    remove_button=self.remove_queue_button,
                                    game=self.game,
                                    queue_selector_buttons=[
                                        self.queue_button_1,
                                        self.queue_button_2,
                                        self.queue_button_3,
                                        self.queue_button_4
                                    ])
        self.screen_image = ScreenImageLabel(emulator=self.emulator,
                                             widget=self.screen_label)
        self.acquire_heroic_quest_rewards_checkbox.stateChanged.connect(
            self.acquire_heroic_quest_rewards_state_changed)
        self.mission_team_spin_box.valueChanged.connect(
            self.mission_team_changed)
        self.timeline_team_spin_box.valueChanged.connect(
            self.timeline_team_changed)
        self.threads = ThreadPool()
        self._user_name_acquired = False
        self.background_functions_to_run()
        self.background_timer = Timer()
        self.background_timer.set_timer(self.background_functions_to_run,
                                        timer_ms=5000)
        self.blockable_buttons = [
            self.run_queue_button, self.add_queue_button,
            self.edit_queue_button, self.remove_queue_button
        ]
        self.tasks = []
        self.create_tasks()

        if self.emulator.initialized and self.emulator.restartable:
            if not self.game.is_main_menu() and not BattleBot(
                    self.game, None).is_battle():
                if not self.game.go_to_main_menu():
                    logger.warning(
                        "Can't get to the main menu. Restarting the game just in case."
                    )
                    self.restart_game_button.click()
        self._create_menu_for_recorder()
예제 #6
0
class MainWindow(QMainWindow, design.Ui_MainWindow):
    """Class for working with main GUI window."""

    recorder = None

    @classmethod
    def pause_recorder(cls):
        if isinstance(cls.recorder, EmulatorCapture):
            cls.recorder.pause()

    @classmethod
    def resume_recorder(cls):
        if isinstance(cls.recorder, EmulatorCapture):
            cls.recorder.resume()

    def __init__(self, file_logger, settings):
        """Class initialization.

        :param logging.FileHandler file_logger: file handler of log-file.
        :param PyQt5.QtCore.QSettings.QSettings settings: GUI settings.
        """
        super().__init__()
        self.setupUi(self)
        set_default_icon(window=self)
        self.settings = settings
        self._init_window_settings()
        self.emulator_name, self.emulator_type, self.game_app_rect, self.emulator, self.game = None, None, None, None, None
        self.load_settings_from_file()
        self.game.file_logger_name = None
        if file_logger:
            self.game.file_logger_name = file_logger.baseFilename
            self.logger = QTextEditFileLogger(
                all_logger_widget=self.logger_text,
                info_logger_widget=self.logger_text_info,
                error_logger_widget=self.logger_text_error,
                log_file=file_logger.baseFilename)
        else:
            self.logger_text.setPlainText(
                "Cannot create log file because `logs` folder doesn't exists.")
        run_and_stop_button = self.create_blockable_button(
            button=self.run_queue_button)
        self.queue_list = QueueList(list_widget=self.queue_list_widget,
                                    run_and_stop_button=run_and_stop_button,
                                    add_button=self.add_queue_button,
                                    edit_button=self.edit_queue_button,
                                    remove_button=self.remove_queue_button,
                                    game=self.game,
                                    queue_selector_buttons=[
                                        self.queue_button_1,
                                        self.queue_button_2,
                                        self.queue_button_3,
                                        self.queue_button_4
                                    ])
        self.screen_image = ScreenImageLabel(emulator=self.emulator,
                                             widget=self.screen_label)
        self.acquire_heroic_quest_rewards_checkbox.stateChanged.connect(
            self.acquire_heroic_quest_rewards_state_changed)
        self.mission_team_spin_box.valueChanged.connect(
            self.mission_team_changed)
        self.timeline_team_spin_box.valueChanged.connect(
            self.timeline_team_changed)
        self.threads = ThreadPool()
        self._user_name_acquired = False
        self.background_functions_to_run()
        self.background_timer = Timer()
        self.background_timer.set_timer(self.background_functions_to_run,
                                        timer_ms=5000)
        self.blockable_buttons = [
            self.run_queue_button, self.add_queue_button,
            self.edit_queue_button, self.remove_queue_button
        ]
        self.tasks = []
        self.create_tasks()

        if self.emulator.initialized and self.emulator.restartable:
            if not self.game.is_main_menu() and not BattleBot(
                    self.game, None).is_battle():
                if not self.game.go_to_main_menu():
                    logger.warning(
                        "Can't get to the main menu. Restarting the game just in case."
                    )
                    self.restart_game_button.click()
        self._create_menu_for_recorder()

    def _init_window_settings(self):
        """Restores the State of a GUI from settings."""
        self.resize(
            self.settings.value("MainWindow/size", defaultValue=self.size()))
        self.move(
            self.settings.value("MainWindow/pos", defaultValue=self.pos()))
        if self.settings.value("MainWindow/isMaximized") == 'true':
            self.showMaximized()

    def background_functions_to_run(self):
        """Runs functions in thread to prevent GUI freezing."""
        self.threads.run_thread(func=self._update_labels)
        self.threads.run_thread(func=self._handle_errors)

    def _update_labels(self):
        """Updates game's labels such as: username, energy, gold and boost points."""
        if not self.emulator.initialized:
            return
        if not self._user_name_acquired and self.game.is_main_menu():
            self.label_username.setText(self.game.user_name)
            self._user_name_acquired = True
        self.label_energy.setText(
            f"Energy: {self.game.energy} / {self.game.energy_max}")
        self.label_gold.setText(f"Gold: {self.game.gold}")
        self.label_boosts.setText(f"Boosts: {self.game.boost} / {100}")

    def _handle_errors(self):
        """Handles network errors by restarting the queue."""
        if not (self.emulator.initialized
                and self.handle_network_errors_checkbox.isChecked()):
            return
        if self.game.close_network_error_notification(
        ) and self.queue_list.process:
            logger.info("Rerunning the queue due network error.")
            self.queue_list.run_and_stop_button.button.click()
            sleep(1)
            self.queue_list.run_and_stop_button.button.click()

    def mission_team_changed(self):
        """'Mission team' spinbox event when value is changed."""
        team = self.mission_team_spin_box.value()
        self.game.set_mission_team(team)
        logger.info(f"Team number for missions : {self.game.mission_team}")
        self.save_settings_to_file()

    def timeline_team_changed(self):
        """'Timeline team' spinbox event when value is changed."""
        team = self.timeline_team_spin_box.value()
        self.game.set_timeline_team(team)
        logger.info(
            f"Team number for TimeLine battles : {self.game.timeline_team}")
        self.save_settings_to_file()

    def acquire_heroic_quest_rewards_state_changed(self):
        """'Acquire heroic quest rewards' checkbox even when value is changed."""
        if self.acquire_heroic_quest_rewards_checkbox.isChecked():
            self.game.ACQUIRE_HEROIC_QUEST_REWARDS = True
        else:
            self.game.ACQUIRE_HEROIC_QUEST_REWARDS = False
        logger.info(
            f"Acquire Heroic Quest rewards: {self.game.ACQUIRE_HEROIC_QUEST_REWARDS}"
        )
        self.save_settings_to_file()

    def load_settings_from_file(self):
        """Loads settings and applies them to game."""
        game_settings = load_game_settings()
        if not game_settings:
            self.setup_gui_first_time()
            return self.load_settings_from_file()
        self.game_app_rect = game_settings.get("game_app_rect")
        self.emulator_name = game_settings.get("emulator_name")
        self.emulator_type = game_settings.get("emulator_type")
        self.timeline_team_spin_box.setValue(
            game_settings.get("timeline_team"))
        self.mission_team_spin_box.setValue(game_settings.get("mission_team"))
        self.acquire_heroic_quest_rewards_checkbox.setChecked(
            game_settings.get("acquire_heroic_quest_rewards", True))
        self.handle_network_errors_checkbox.setChecked(
            game_settings.get("handle_network_errors", False))
        self.init_emulator_and_game()
        self.game.set_mission_team(self.mission_team_spin_box.value())
        self.game.set_timeline_team(self.timeline_team_spin_box.value())
        self.game.ACQUIRE_HEROIC_QUEST_REWARDS = self.acquire_heroic_quest_rewards_checkbox.isChecked(
        )

    def save_settings_to_file(self):
        """Stores GUI settings to file."""
        game_settings = {
            "timeline_team":
            self.game.timeline_team,
            "mission_team":
            self.game.mission_team,
            "acquire_heroic_quest_rewards":
            self.game.ACQUIRE_HEROIC_QUEST_REWARDS,
            "handle_network_errors":
            self.handle_network_errors_checkbox.isChecked(),
            "emulator_name":
            self.emulator_name,
            "emulator_type":
            self.emulator_type,
            "game_app_rect":
            self.game_app_rect
        }
        save_game_settings(game_settings)
        logger.debug("Game settings saved.")

    def setup_gui_first_time(self):
        """Setups GUI settings for first time.
        Runs `SetupEmulator` and retrieves information about emulator and game app."""
        setup = SetupEmulator()
        setup.run_emulator_setup()
        self.emulator_name, self.emulator_type, self.game_app_rect = setup.get_emulator_and_game_app(
        )
        game_settings = {
            "timeline_team":
            self.timeline_team_spin_box.value(),
            "mission_team":
            self.mission_team_spin_box.value(),
            "acquire_heroic_quest_rewards":
            self.acquire_heroic_quest_rewards_checkbox.isChecked(),
            "handle_network_errors":
            self.handle_network_errors_checkbox.isChecked(),
            "emulator_name":
            self.emulator_name,
            "emulator_type":
            self.emulator_type,
            "game_app_rect":
            self.game_app_rect
        }
        save_game_settings(game_settings)
        logger.debug("Saving setting from first setup.")

    def init_emulator_and_game(self):
        """Initializes emulator and game objects."""
        if not self.emulator_name:
            self.setup_gui_first_time()
        if self.emulator_type == NoxPlayer.__name__:
            self.emulator = NoxPlayer(self.emulator_name)
            if self.emulator.get_version(
            ) and self.emulator.get_version() < LooseVersion('7.0.0.0'):
                menu = self.menuBar.addMenu("Emulator")
                action = menu.addAction(
                    f"Make {self.emulator.name} restartable")
                action.triggered.connect(self.emulator.set_config_for_bot)
        if self.emulator_type == BlueStacks.__name__:
            self.emulator = BlueStacks(self.emulator_name)
        if not self.emulator.restartable:
            self.restart_game_button.setEnabled(False)
            self.restart_game_button.setText(
                f"{self.restart_game_button.text()}\n"
                "[Unavailable (check logs)]")
            self.restart_game_button = None
        self.game = Game(self.emulator)
        self.manager = SyncManager()
        self.manager.start()
        self.game._modes = self.manager.dict()
        if self.game_app_rect:
            self.game._game_app_ui.button_rect = Rect(*self.game_app_rect)

    def _create_menu_for_recorder(self):
        """Creates menu bar for emulator recording."""
        menu = self.menuBar.addMenu("Video Recorder")
        self.recorder_action = menu.addAction("Start recording")
        self.recorder_action.triggered.connect(self._start_recording)

    def _start_recording(self):
        """Starts recording video from emulator."""
        MainWindow.recorder = EmulatorCapture(self.emulator)
        MainWindow.recorder.start()
        MainWindow.recorder.pause()

        self.recorder_action.setText("Stop recording")
        try_to_disconnect(self.recorder_action.triggered,
                          self._start_recording)
        self.recorder_action.triggered.connect(self._stop_recording)

        self.screen_image.get_image_func = lambda: bgr_to_rgb(
            MainWindow.recorder.video_capture.source.frame())

    def _stop_recording(self):
        """Stops recording video from emulator."""
        MainWindow.recorder.stop()
        MainWindow.recorder = None

        self.recorder_action.setText("Start recording")
        try_to_disconnect(self.recorder_action.triggered, self._stop_recording)
        self.recorder_action.triggered.connect(self._start_recording)

        self.screen_image.get_image_func = self.emulator.get_screen_image

    def closeEvent(self, event):
        """Main window close event."""
        self.queue_list.stop_queue()
        for task in self.tasks:
            task.abort()
        self.save_settings_to_file()
        self.queue_list.save_queue_to_file()
        self.settings.setValue("MainWindow/size", self.size())
        self.settings.setValue("MainWindow/isMaximized", self.isMaximized())
        self.settings.setValue("MainWindow/pos", self.pos())

    def create_blockable_button(self, button):
        """Creates button that blocks others."""
        if not button:
            return
        two_state_button = TwoStateButton(button=button)
        two_state_button.connect_first_state(self.block_buttons,
                                             caller_button=button)
        two_state_button.connect_second_state(self.unblock_buttons)
        two_state_button.signals.first_state.connect(self.unblock_buttons)
        return two_state_button

    def block_buttons(self, caller_button):
        """Blocks buttons except caller one.

        :param PyQt5.QtWidgets.QPushButton.QPushButton caller_button: button that called the event.
        """
        buttons_to_block = [
            button for button in self.blockable_buttons
            if button and button.isEnabled() and button != caller_button
        ]
        for button in buttons_to_block:
            button.setEnabled(False)

    def unblock_buttons(self):
        """Unblocks all buttons."""
        for button in self.blockable_buttons:
            if button:
                button.setEnabled(True)

    def _create_task(self, button, task_class):
        """Creates blockable button for task and initialized it.

        :param PyQt5.QtWidgets.QPushButton.QPushButton button: button to run the task.
        :param type[lib.gui.single_task_manager.SingleTask] task_class: class object of the task.
        """
        blockable_button = self.create_blockable_button(button=button)
        task = task_class(game=self.game, button=blockable_button)
        self.blockable_buttons.append(button)
        self.tasks.append(task)

    def create_tasks(self):
        """Creates all available tasks."""
        self._create_task(button=self.autoplay_button, task_class=AutoPlayTask)
        self._create_task(button=self.daily_trivia_button,
                          task_class=DailyTriviaTask)
        self._create_task(button=self.world_boss_invasion_button,
                          task_class=WorldBossInvasionTask)
        self._create_task(button=self.squad_battle_button,
                          task_class=SquadBattleAllTask)
        self._create_task(button=self.danger_room_button,
                          task_class=DangerRoomOneBattleTask)
        self._create_task(button=self.restart_game_button,
                          task_class=RestartGameTask)
        self._create_task(button=self.comic_cards_button,
                          task_class=ComicCardsTask)
        self._create_task(button=self.custom_gear_button,
                          task_class=CustomGearTask)
        self._create_task(button=self.dispatch_mission_rewards,
                          task_class=DispatchMissionAcquireTask)
        self._create_task(button=self.enhance_potential_button,
                          task_class=EnhancePotentialTask)
        self._create_task(button=self.shadowland_button,
                          task_class=ShadowlandAllFloorsTask)
예제 #7
0
class SingleTaskWithOptions:
    def __init__(self, button, task_func, task_options):
        """Class initialization.

        :param TwoStateButton button: button that activates task.
        :param task_func: function to execute.
        :param dict task_options: function's parameters by option's key.
        """
        self.run_and_stop_button = button
        self.task_func = task_func
        self.task_options = task_options
        self.threads = ThreadPool()
        self.process = None
        for task_name, task_parameters in task_options.items():

            def add_action(parameters):
                self.run_and_stop_button.add_action(
                    task_name, lambda: self.execute(parameters))

            add_action(task_parameters)
        self.menu = self.run_and_stop_button.button.menu()
        self.run_and_stop_button.connect_second_state(self.abort)

    def execute(self, parameters):
        """Execute function in safe thread."""
        logger.debug(
            f"Executing single task: {self.__class__.__name__} {self.task_func.__name__} "
            f"with parameters {parameters}")
        from lib.gui.widgets.main import MainWindow
        MainWindow.resume_recorder()
        worker = self.threads.run_thread(
            target=lambda: self._execute(parameters=parameters))
        worker.signals.finished.connect(
            self.run_and_stop_button.set_first_state)
        worker.signals.finished.connect(self._set_menu)
        worker.signals.finished.connect(MainWindow.pause_recorder)
        self._clear_menu()
        self.run_and_stop_button.set_second_state()

    def _clear_menu(self):
        """Clear button menu."""
        self.run_and_stop_button.button.setMenu(None)

    def _set_menu(self):
        """Set button menu from cache."""
        self.run_and_stop_button.button.setMenu(self.menu)

    @safe_process_stop
    def abort(self):
        """Abort function's execution."""
        if self.process:
            logger.debug("Task was forcibly stopped.")
            self.process.terminate()
        self.threads.thread_pool.clear()
        self._set_menu()
        self.run_and_stop_button.set_first_state()
        from lib.gui.widgets.main import MainWindow
        MainWindow.pause_recorder()

    def _execute(self, parameters):
        """Execute function."""
        self.process = Process(target=self.task_func, kwargs=parameters)
        self.process.start()
        self.process.join()
        logger.debug("Task completed.")
예제 #8
0
class QueueList:
    """Class for working with queue list."""

    def __init__(self, game, list_widget, run_and_stop_button, add_button, edit_button, remove_button,
                 queue_selector_buttons):
        """Class initialization.

        :param PyQt5.QtWidgets.QListWidget.QListWidget list_widget: list widget.
        :param lib.gui.helper.TwoStateButton run_and_stop_button: button for running the queue.
        :param PyQt5.QtWidgets.QPushButton.QPushButton add_button: button for adding new element to queue.
        :param PyQt5.QtWidgets.QPushButton.QPushButton edit_button: button for editing existing element in the queue.
        :param PyQt5.QtWidgets.QPushButton.QPushButton remove_button:
            button for removing existing element from the queue.
        :param list[PyQt5.QtWidgets.QPushButton.QPushButton] queue_selector_buttons:
            list of buttons for selecting different queues.
        """
        self.game = game
        self.widget = list_widget
        self.run_and_stop_button = run_and_stop_button
        self.add_button = add_button
        self.edit_button = edit_button
        self.remove_button = remove_button
        self.queue_selector_buttons = queue_selector_buttons
        self.stored_queues = [[], ] * len(self.queue_selector_buttons)  # type: List[List[QueueItem]]
        self.current_queue_index = 0
        self.setup_buttons()
        self.threads = ThreadPool()
        self.process = None
        self.add_button.clicked.connect(self.add)
        if self.widget.count() == 0:
            self.run_and_stop_button.button.setEnabled(False)
        self.select_all_item = self.add_select_all_checkbox()
        self.widget.itemChanged.connect(self.on_item_change)
        self.load_queue_from_file()
        self.change_select_all_state()
        self.stop_queue_flag = False

    def queue(self):
        """Queue iterator."""
        for i in range(self.widget.count()):
            item = self.widget.item(i)
            if isinstance(item, QueueItem):
                yield item

    @property
    def queue_fifo(self):
        """Creates FIFO representation of current queue.

        :rtype: deque[QueueItem]
        """
        return deque(list(self.queue()))

    def clear_queue(self):
        """Clears queue."""
        for _ in range(self.widget.count()):
            item = self.widget.item(0)
            self.widget.takeItem(self.widget.row(item))

    def store_current_queue(self):
        """Stores currently selected queue to variable."""
        self.stored_queues[self.current_queue_index] = [*self.queue()]

    def change_queue(self, index):
        """Changes queue by index.

        :param int index: queue's index.
        """
        if index != self.current_queue_index:
            self.store_current_queue()
        self.current_queue_index = index
        self.clear_queue()
        self.select_all_item = self.add_select_all_checkbox()
        for item in self.stored_queues[index]:
            self._add(item)
        for button in self.queue_selector_buttons:
            button.setChecked(False)
        self.queue_selector_buttons[index].setChecked(True)

    def add_select_all_checkbox(self):
        """Creates 'Select All' checkbox with empty line below."""
        select_all = QListWidgetItem()
        select_all.setText("[Select All]")
        select_all.setCheckState(Qt.Checked)
        select_all.setFlags(select_all.flags() | Qt.ItemIsUserCheckable)
        select_all.setFlags(select_all.flags() ^ Qt.ItemIsDragEnabled)
        select_all.setFlags(select_all.flags() ^ Qt.ItemIsSelectable)
        blank_line = QListWidgetItem()
        blank_line.setFlags(blank_line.flags() ^ Qt.ItemIsDragEnabled)
        blank_line.setFlags(blank_line.flags() ^ Qt.ItemIsSelectable)
        self.widget.addItem(select_all)
        self.widget.addItem(blank_line)
        return select_all

    def change_select_all_state(self):
        """Changes 'Select All' checkbox state by queue item's states."""
        queue_states = [queue_item.checkState() for queue_item in self.queue()]
        all_checked = [state for state in queue_states if state == Qt.Checked]
        all_unchecked = [state for state in queue_states if state == Qt.Unchecked]
        partially_checked = all_checked and all_unchecked
        if all_checked and not all_unchecked:
            self.select_all_item.setCheckState(Qt.Checked)
        if all_unchecked and not all_checked:
            self.select_all_item.setCheckState(Qt.Unchecked)
        if partially_checked:
            self.select_all_item.setCheckState(Qt.PartiallyChecked)

    def on_item_change(self, item):
        """Selects or deselects items when some item was checked.

        :param QListWidgetItem item: changed item.
        """
        if item == self.select_all_item:
            state = item.checkState()
            if state == Qt.Checked:
                self.select_all()
            if state == Qt.Unchecked:
                self.deselect_all()
        if isinstance(item, QueueItem):
            if len(self.widget.selectedItems()) > 1:
                for selected_item in self.widget.selectedItems():
                    selected_item.setCheckState(item.checkState())
            self.change_select_all_state()

    def load_queue_from_file(self):
        """Loads queue list and apply it to GUI."""
        queues_list = load_queue_list()
        if not queues_list:
            return
        for queue_index, queue in enumerate(queues_list):
            logger.debug(f"Loading {len(queue)} items to queue list #{queue_index + 1}.")
            queue_items = []
            for settings in queue:
                editor = QueueItemEditor.from_settings(game=self.game, settings=settings)
                if editor:
                    item = editor.render_queue_item()
                    item.set_checked(settings.get("checked", False))
                    queue_items.append(item)
            self.stored_queues[queue_index] = queue_items
        self.change_queue(index=0)

    def save_queue_to_file(self):
        """Saves existing queue to JSON-file."""
        self.store_current_queue()
        queues_list = []
        for queue_index, queue in enumerate(self.stored_queues):
            queue_items = []
            for item in queue:
                settings = {
                    "mode_name": item.mode_name,
                    "checked": item.is_checked,
                    **item.parameters
                }
                queue_items.append(settings)
            queues_list.append(queue_items)
            logger.debug(f"Saving queue #{queue_index + 1} list with {len(queue)} items.")
        save_queue_list(queues_list)

    def setup_buttons(self):
        """Setups button's events."""
        self.run_and_stop_button.connect_first_state(self.run_queue)
        self.run_and_stop_button.connect_second_state(self.stop_queue)
        self.run_and_stop_button.connect_first_state(self.widget.setDragDropMode, QAbstractItemView.NoDragDrop)
        self.run_and_stop_button.connect_second_state(self.widget.setDragDropMode, QAbstractItemView.InternalMove)
        self.remove_button.clicked.connect(self.remove_current_item)
        self.edit_button.clicked.connect(self.edit_current_item)

        # Setup Queue #1 and etc. buttons to change queue
        def change_queue_on_click(button, queue_index):
            button.clicked.connect(lambda: self.change_queue(queue_index))

        for index in range(len(self.queue_selector_buttons)):
            change_queue_on_click(button=self.queue_selector_buttons[index], queue_index=index)

    def add(self):
        """Creates editor window and add queue item from it."""
        editor = QueueItemEditor(game=self.game)
        editor.setWindowTitle("Add queue item")
        result = editor.exec_()
        if result and editor.queue_item:
            self._add(editor.queue_item)

    def _add(self, item):
        """Adds item to queue.

        :param QListWidgetItem item: item to add.
        """
        if self.widget.count() == 2:
            self.run_and_stop_button.button.setEnabled(True)
        self.widget.addItem(item)
        self.change_select_all_state()
        return item

    def edit_current_item(self):
        """Edits current selected item."""
        item = self.widget.currentItem()
        if item and isinstance(item, QueueItem):
            editor = QueueItemEditor.from_result_item(game=self.game, queue_item=item)
            editor.setWindowTitle("Edit queue item")
            result = editor.exec_()
            if result and editor.queue_item:
                self.edit_item(old_item=item, new_item=editor.queue_item)

    def edit_item(self, old_item, new_item):
        """Edits queue item.

        :param QListWidgetItem old_item: item before editing.
        :param QListWidgetItem new_item: item after editing.
        """
        widget_index = self.widget.row(old_item)
        self.widget.takeItem(widget_index)
        self.widget.insertItem(widget_index, new_item)
        self.widget.setCurrentRow(widget_index)

    def remove_current_item(self):
        """Removes current selected item from queue."""
        item = self.widget.currentItem()
        if item and isinstance(item, QueueItem):
            if len(self.widget.selectedItems()) > 1:
                for selected_item in self.widget.selectedItems():
                    self.remove_item(selected_item)
            self.remove_item(item)

    def remove_item(self, item):
        """Removes item from queue.

        :param QListWidgetItem item: queue item.
        """
        self.widget.takeItem(self.widget.row(item))
        self.change_select_all_state()
        if self.widget.count() == 2:
            self.run_and_stop_button.button.setEnabled(False)

    def add_queue_by_index(self, queue, queue_index):
        """Adds items from queue to current queue by queue index.

        :param deque[QueueItem] queue: current FIFO queue.
        :param queue_index: index of queue to add.
        """
        logger.debug(f"Running queue by index = {queue_index}")
        for item in reversed(self.stored_queues[queue_index - 1]):
            clone = item.clone(mark=True)
            queue.appendleft(clone)

    def run_queue(self):
        """Runs and executes all items in queue."""
        logger.debug("Running queue.")
        self.store_current_queue()
        from lib.gui.widgets.main import MainWindow
        MainWindow.resume_recorder()
        self.run_and_stop_button.set_second_state()
        self.widget.setDragDropMode(QAbstractItemView.NoDragDrop)
        self.threads.run_thread(func=self._run_queue,
                                on_finish=[self.run_and_stop_button.set_first_state,
                                           self.reset_background,
                                           MainWindow.pause_recorder],
                                on_progress=self.mark_execution_background,
                                with_progress=True)

    def mark_execution_background(self, cur_index):
        """Marks execution queue items with color.

        :param int cur_index: index for current item in queue.
        """
        for index, item in enumerate(self.queue()):
            if index == cur_index:
                item.setBackground(Qt.yellow)
                break
            color = Qt.green if item.is_checked else Qt.gray
            item.setBackground(color)

    def reset_background(self):
        """Resets queue colors."""
        for item in self.queue():
            item.setBackground(Qt.transparent)

    @safe_process_stop
    def stop_queue(self):
        """Stops queue execution."""
        from lib.gui.widgets.main import MainWindow
        MainWindow.pause_recorder()
        self.game.clear_modes()
        self.widget.setDragDropMode(QAbstractItemView.InternalMove)
        self.stop_queue_flag = True
        if self.process:
            logger.debug("Queue was forcibly stopped.")
            self.process.terminate()
        self.threads.thread_pool.clear()
        self.run_and_stop_button.set_first_state()

    def _run_queue(self, progress_callback):
        """Runs item's execution.

        :param PyQt5.QtCore.pyqtSignal.pyqtSignal progress_callback: signal to emit queue progress.
        """
        queue = self.queue_fifo
        index = -1
        while queue:
            item = queue.popleft()
            if not item.was_cloned:
                index += 1
            if self.stop_queue_flag:
                break
            progress_callback.emit(index)
            executor, settings = item.get_executor()
            if item.mode_name == "RUN QUEUE" and item.is_checked:
                self.add_queue_by_index(queue=queue, **settings)
                continue
            if not executor:
                logger.debug(f"Skipping queue item: {item.mode_name}")
                continue
            logger.debug(f"Running {item.mode_name} with settings: {settings}")
            self.process = Process(target=executor, kwargs=settings)
            self.process.start()
            self.process.join()
        self.stop_queue_flag = False
        self.widget.setDragDropMode(QAbstractItemView.InternalMove)
        self.game.clear_modes()
        logger.debug("Queue completed.")

    def select_all(self):
        """Selects all items in queue."""
        for item in self.queue():
            item.set_checked(True)

    def deselect_all(self):
        """Deselects all items in queue."""
        for item in self.queue():
            item.set_checked(False)
예제 #9
0
    def __init__(self, file_logger):
        """Class initialization."""
        super().__init__()
        self.setupUi(self)
        set_default_icon(window=self)
        self.emulator_name, self.emulator_type, self.game_app_rect, self.emulator, self.game = None, None, None, None, None
        self.load_settings_from_file()
        self.game.file_logger_name = None
        if file_logger:
            self.game.file_logger_name = file_logger.baseFilename
            self.logger = QTextEditFileLogger(
                logger_widget=self.logger_text,
                log_file=file_logger.baseFilename)
        else:
            self.logger_text.setPlainText(
                "Cannot create log file because `logs` folder doesn't exists.")
        run_and_stop_button = self.create_blockable_button(
            button=self.run_queue_button)
        autoplay_button = self.create_blockable_button(
            button=self.autoplay_button)
        daily_trivia_button = self.create_blockable_button(
            button=self.daily_trivia_button)
        world_boss_invasion_button = self.create_blockable_button(
            button=self.world_boss_invasion_button)
        squad_battle_button = self.create_blockable_button(
            button=self.squad_battle_button)
        danger_room_button = self.create_blockable_button(
            button=self.danger_room_button)
        shield_lab_button = self.create_blockable_button(
            button=self.shield_lab_button)
        restart_game_button = self.create_blockable_button(
            button=self.restart_game_button)
        comic_cards_button = self.create_blockable_button(
            button=self.comic_cards_button)
        custom_gear_button = self.create_blockable_button(
            button=self.custom_gear_button)
        dispatch_mission_button = self.create_blockable_button(
            button=self.dispatch_mission_rewards)
        self.queue_list = QueueList(list_widget=self.queue_list_widget,
                                    run_and_stop_button=run_and_stop_button,
                                    add_button=self.add_queue_button,
                                    edit_button=self.edit_queue_button,
                                    remove_button=self.remove_queue_button,
                                    game=self.game,
                                    queue_selector_buttons=[
                                        self.queue_button_1,
                                        self.queue_button_2,
                                        self.queue_button_3,
                                        self.queue_button_4
                                    ])
        self.autoplay = AutoPlayTask(game=self.game, button=autoplay_button)
        self.daily_trivia = DailyTriviaTask(game=self.game,
                                            button=daily_trivia_button)
        self.world_boss_invasion = WorldBossInvasionTask(
            game=self.game, button=world_boss_invasion_button)
        self.squad_battle = SquadBattleAllTask(game=self.game,
                                               button=squad_battle_button)
        self.danger_room = DangerRoomOneBattleTask(game=self.game,
                                                   button=danger_room_button)
        self.shield_lab = ShieldLabCollectAntimatterOneBattleTask(
            game=self.game, button=shield_lab_button)
        self.restart_game = RestartGameTask(game=self.game,
                                            button=restart_game_button)
        self.comic_cards = ComicCardsTask(game=self.game,
                                          button=comic_cards_button)
        self.custom_gear = CustomGearTask(game=self.game,
                                          button=custom_gear_button)
        self.dispatch_mission = DispatchMissionAcquireTask(
            game=self.game, button=dispatch_mission_button)
        self.screen_image = ScreenImageLabel(emulator=self.emulator,
                                             widget=self.screen_label)
        self.acquire_heroic_quest_rewards_checkbox.stateChanged.connect(
            self.acquire_heroic_quest_rewards_state_changed)
        self.low_memory_mode_checkbox.stateChanged.connect(
            self.low_memory_mode_state_changed)
        self.mission_team_spin_box.valueChanged.connect(
            self.mission_team_changed)
        self.timeline_team_spin_box.valueChanged.connect(
            self.timeline_team_changed)
        self.threads = ThreadPool()
        self._user_name_acquired = False
        self.update_labels()
        self.label_timer = Timer()
        self.label_timer.set_timer(self.update_labels, timer_ms=5000)
        self.blockable_buttons = [
            self.run_queue_button, self.add_queue_button,
            self.edit_queue_button, self.remove_queue_button,
            self.squad_battle_button, self.world_boss_invasion_button,
            self.daily_trivia_button, self.autoplay_button,
            self.danger_room_button, self.shield_lab_button,
            self.restart_game_button, self.comic_cards_button,
            self.custom_gear_button, self.dispatch_mission_rewards
        ]
        self.tasks = [
            self.autoplay, self.daily_trivia, self.world_boss_invasion,
            self.squad_battle, self.danger_room, self.shield_lab,
            self.restart_game, self.comic_cards, self.custom_gear,
            self.dispatch_mission
        ]

        if self.emulator.initialized and self.emulator.restartable:
            if not self.game.is_main_menu() and not BattleBot(
                    self.game, None).is_battle():
                if not self.game.go_to_main_menu():
                    logger.warning(
                        "Can't get to the main menu. Restarting the game just in case."
                    )
                    self.restart_game_button.click()
        self._create_menu_for_recorder()
예제 #10
0
class MainWindow(QMainWindow, design.Ui_MainWindow):
    """Class for working with main GUI window."""

    recorder = None

    @classmethod
    def pause_recorder(cls):
        if isinstance(cls.recorder, EmulatorCapture):
            cls.recorder.pause()

    @classmethod
    def resume_recorder(cls):
        if isinstance(cls.recorder, EmulatorCapture):
            cls.recorder.resume()

    def __init__(self, file_logger):
        """Class initialization."""
        super().__init__()
        self.setupUi(self)
        set_default_icon(window=self)
        self.emulator_name, self.emulator_type, self.game_app_rect, self.emulator, self.game = None, None, None, None, None
        self.load_settings_from_file()
        self.game.file_logger_name = None
        if file_logger:
            self.game.file_logger_name = file_logger.baseFilename
            self.logger = QTextEditFileLogger(
                logger_widget=self.logger_text,
                log_file=file_logger.baseFilename)
        else:
            self.logger_text.setPlainText(
                "Cannot create log file because `logs` folder doesn't exists.")
        run_and_stop_button = self.create_blockable_button(
            button=self.run_queue_button)
        autoplay_button = self.create_blockable_button(
            button=self.autoplay_button)
        daily_trivia_button = self.create_blockable_button(
            button=self.daily_trivia_button)
        world_boss_invasion_button = self.create_blockable_button(
            button=self.world_boss_invasion_button)
        squad_battle_button = self.create_blockable_button(
            button=self.squad_battle_button)
        danger_room_button = self.create_blockable_button(
            button=self.danger_room_button)
        shield_lab_button = self.create_blockable_button(
            button=self.shield_lab_button)
        restart_game_button = self.create_blockable_button(
            button=self.restart_game_button)
        comic_cards_button = self.create_blockable_button(
            button=self.comic_cards_button)
        custom_gear_button = self.create_blockable_button(
            button=self.custom_gear_button)
        dispatch_mission_button = self.create_blockable_button(
            button=self.dispatch_mission_rewards)
        self.queue_list = QueueList(list_widget=self.queue_list_widget,
                                    run_and_stop_button=run_and_stop_button,
                                    add_button=self.add_queue_button,
                                    edit_button=self.edit_queue_button,
                                    remove_button=self.remove_queue_button,
                                    game=self.game,
                                    queue_selector_buttons=[
                                        self.queue_button_1,
                                        self.queue_button_2,
                                        self.queue_button_3,
                                        self.queue_button_4
                                    ])
        self.autoplay = AutoPlayTask(game=self.game, button=autoplay_button)
        self.daily_trivia = DailyTriviaTask(game=self.game,
                                            button=daily_trivia_button)
        self.world_boss_invasion = WorldBossInvasionTask(
            game=self.game, button=world_boss_invasion_button)
        self.squad_battle = SquadBattleAllTask(game=self.game,
                                               button=squad_battle_button)
        self.danger_room = DangerRoomOneBattleTask(game=self.game,
                                                   button=danger_room_button)
        self.shield_lab = ShieldLabCollectAntimatterOneBattleTask(
            game=self.game, button=shield_lab_button)
        self.restart_game = RestartGameTask(game=self.game,
                                            button=restart_game_button)
        self.comic_cards = ComicCardsTask(game=self.game,
                                          button=comic_cards_button)
        self.custom_gear = CustomGearTask(game=self.game,
                                          button=custom_gear_button)
        self.dispatch_mission = DispatchMissionAcquireTask(
            game=self.game, button=dispatch_mission_button)
        self.screen_image = ScreenImageLabel(emulator=self.emulator,
                                             widget=self.screen_label)
        self.acquire_heroic_quest_rewards_checkbox.stateChanged.connect(
            self.acquire_heroic_quest_rewards_state_changed)
        self.low_memory_mode_checkbox.stateChanged.connect(
            self.low_memory_mode_state_changed)
        self.mission_team_spin_box.valueChanged.connect(
            self.mission_team_changed)
        self.timeline_team_spin_box.valueChanged.connect(
            self.timeline_team_changed)
        self.threads = ThreadPool()
        self._user_name_acquired = False
        self.update_labels()
        self.label_timer = Timer()
        self.label_timer.set_timer(self.update_labels, timer_ms=5000)
        self.blockable_buttons = [
            self.run_queue_button, self.add_queue_button,
            self.edit_queue_button, self.remove_queue_button,
            self.squad_battle_button, self.world_boss_invasion_button,
            self.daily_trivia_button, self.autoplay_button,
            self.danger_room_button, self.shield_lab_button,
            self.restart_game_button, self.comic_cards_button,
            self.custom_gear_button, self.dispatch_mission_rewards
        ]
        self.tasks = [
            self.autoplay, self.daily_trivia, self.world_boss_invasion,
            self.squad_battle, self.danger_room, self.shield_lab,
            self.restart_game, self.comic_cards, self.custom_gear,
            self.dispatch_mission
        ]

        if self.emulator.initialized and self.emulator.restartable:
            if not self.game.is_main_menu() and not BattleBot(
                    self.game, None).is_battle():
                if not self.game.go_to_main_menu():
                    logger.warning(
                        "Can't get to the main menu. Restarting the game just in case."
                    )
                    self.restart_game_button.click()
        self._create_menu_for_recorder()

    def update_labels(self):
        """Update game's labels in thread to prevent GUI freezing."""
        self.threads.run_thread(target=self._update_labels)

    def _update_labels(self):
        """Update game's labels such as: username, energy, gold and boost points."""
        if not self.emulator.initialized:
            return
        if not self._user_name_acquired and self.game.is_main_menu():
            self.label_username.setText(self.game.user_name)
            self._user_name_acquired = True
        self.label_energy.setText(
            f"Energy: {self.game.energy} / {self.game.energy_max}")
        self.label_gold.setText(f"Gold: {self.game.gold}")
        self.label_boosts.setText(f"Boosts: {self.game.boost} / {100}")

    def mission_team_changed(self):
        """'Mission team' spinbox event when value is changed."""
        team = self.mission_team_spin_box.value()
        self.game.set_mission_team(team)
        logger.info(f"Team number for missions : {self.game.mission_team}")
        self.save_settings_to_file()

    def timeline_team_changed(self):
        """'Timeline team' spinbox event when value is changed."""
        team = self.timeline_team_spin_box.value()
        self.game.set_timeline_team(team)
        logger.info(
            f"Team number for TimeLine battles : {self.game.timeline_team}")
        self.save_settings_to_file()

    def acquire_heroic_quest_rewards_state_changed(self):
        """'Acquire heroic quest rewards' checkbox even when value is changed."""
        if self.acquire_heroic_quest_rewards_checkbox.isChecked():
            self.game.ACQUIRE_HEROIC_QUEST_REWARDS = True
        else:
            self.game.ACQUIRE_HEROIC_QUEST_REWARDS = False
        logger.info(
            f"Acquire Heroic Quest rewards: {self.game.ACQUIRE_HEROIC_QUEST_REWARDS}"
        )
        self.save_settings_to_file()

    def low_memory_mode_state_changed(self):
        """'Low memory mode' checkbox even when value is changed."""
        if self.low_memory_mode_checkbox.isChecked():
            self.game.LOW_MEMORY_MODE = True
        else:
            self.game.LOW_MEMORY_MODE = False
        logger.info(f"Low memory mode: {self.game.LOW_MEMORY_MODE}")
        self.save_settings_to_file()

    def load_settings_from_file(self):
        """Load settings and apply them to game."""
        game_settings = load_game_settings()
        if not game_settings:
            self.setup_gui_first_time()
            return self.load_settings_from_file()
        self.game_app_rect = game_settings.get("game_app_rect")
        self.emulator_name = game_settings.get("emulator_name")
        self.emulator_type = game_settings.get("emulator_type")
        self.timeline_team_spin_box.setValue(
            game_settings.get("timeline_team"))
        self.mission_team_spin_box.setValue(game_settings.get("mission_team"))
        self.acquire_heroic_quest_rewards_checkbox.setChecked(
            game_settings.get("acquire_heroic_quest_rewards", True))
        self.low_memory_mode_checkbox.setChecked(
            game_settings.get("low_memory_mode", False))
        # TODO: backwards compatability, remove after few updates
        self.emulator_name = game_settings[
            'player_name'] if 'player_name' in game_settings else self.emulator_name
        self.emulator_type = game_settings[
            'player_type'] if 'player_type' in game_settings else self.emulator_type
        # TODO: end
        self.init_emulator_and_game()
        self.game.set_mission_team(self.mission_team_spin_box.value())
        self.game.set_timeline_team(self.timeline_team_spin_box.value())
        self.game.ACQUIRE_HEROIC_QUEST_REWARDS = self.acquire_heroic_quest_rewards_checkbox.isChecked(
        )
        self.game.LOW_MEMORY_MODE = self.low_memory_mode_checkbox.isChecked()

    def save_settings_to_file(self):
        """Store GUI settings to file."""
        game_settings = {
            "timeline_team": self.game.timeline_team,
            "mission_team": self.game.mission_team,
            "acquire_heroic_quest_rewards":
            self.game.ACQUIRE_HEROIC_QUEST_REWARDS,
            "low_memory_mode": self.game.LOW_MEMORY_MODE,
            "emulator_name": self.emulator_name,
            "emulator_type": self.emulator_type,
            "game_app_rect": self.game_app_rect
        }
        save_game_settings(game_settings)
        logger.debug("Game settings saved.")

    def setup_gui_first_time(self):
        """Setup GUI settings for first time.
        Run `SetupEmulator` and retrieve information about emulator and game app."""
        setup = SetupEmulator()
        setup.run_emulator_setup()
        self.emulator_name, self.emulator_type, self.game_app_rect = setup.get_emulator_and_game_app(
        )
        game_settings = {
            "timeline_team":
            self.timeline_team_spin_box.value(),
            "mission_team":
            self.mission_team_spin_box.value(),
            "acquire_heroic_quest_rewards":
            self.acquire_heroic_quest_rewards_checkbox.isChecked(),
            "low_memory_mode":
            self.low_memory_mode_checkbox.isChecked(),
            "emulator_name":
            self.emulator_name,
            "emulator_type":
            self.emulator_type,
            "game_app_rect":
            self.game_app_rect
        }
        save_game_settings(game_settings)
        logger.debug("Saving setting from first setup.")

    def init_emulator_and_game(self):
        """Init emulator and game."""
        if not self.emulator_name:
            self.setup_gui_first_time()
        # TODO: backwards compatability, remove after few updates
        if self.emulator_type == "NoxWindow":
            self.emulator_type = NoxPlayer.__name__
        # TODO: end
        if self.emulator_type == NoxPlayer.__name__:
            self.emulator = NoxPlayer(self.emulator_name)
            if self.emulator.get_version(
            ) and not self.emulator.get_version().startswith("7."):
                menu = self.menuBar.addMenu("Emulator")
                action = menu.addAction(
                    f"Make {self.emulator.name} restartable")
                action.triggered.connect(self.emulator.set_config_for_bot)
        if self.emulator_type == BlueStacks.__name__:
            self.emulator = BlueStacks(self.emulator_name)
        if not self.emulator.restartable:
            self.restart_game_button.setEnabled(False)
            self.restart_game_button.setText(
                f"{self.restart_game_button.text()}\n"
                "[Unavailable (check logs)]")
            self.restart_game_button = None
        self.game = Game(self.emulator)
        if self.game_app_rect:
            self.game.ui['GAME_APP'].button = Rect(*self.game_app_rect)

    def _create_menu_for_recorder(self):
        """Creates menu bar for emulator recording."""
        menu = self.menuBar.addMenu("Video Recorder")
        self.recorder_action = menu.addAction("Start recording")
        self.recorder_action.triggered.connect(self._start_recording)

    def _start_recording(self):
        """Start recording video from emulator."""
        MainWindow.recorder = EmulatorCapture(self.emulator)
        MainWindow.recorder.start()
        MainWindow.recorder.pause()

        self.recorder_action.setText("Stop recording")
        try_to_disconnect(self.recorder_action.triggered,
                          self._start_recording)
        self.recorder_action.triggered.connect(self._stop_recording)

        self.screen_image.get_image_func = lambda: bgr_to_rgb(
            MainWindow.recorder.video_capture.source.frame())

    def _stop_recording(self):
        """Stop recording video from emulator."""
        MainWindow.recorder.stop()
        MainWindow.recorder = None

        self.recorder_action.setText("Start recording")
        try_to_disconnect(self.recorder_action.triggered, self._stop_recording)
        self.recorder_action.triggered.connect(self._start_recording)

        self.screen_image.get_image_func = self.emulator.get_screen_image

    def closeEvent(self, event):
        """Main window close event."""
        self.queue_list.stop_queue()
        for task in self.tasks:
            task.abort()
        self.save_settings_to_file()
        self.queue_list.save_queue_to_file()

    def create_blockable_button(self, button):
        """Create button that blocks others."""
        if not button:
            return
        two_state_button = TwoStateButton(button=button)
        two_state_button.connect_first_state(self.block_buttons,
                                             caller_button=button)
        two_state_button.connect_second_state(self.unblock_buttons)
        two_state_button.signals.first_state.connect(self.unblock_buttons)
        return two_state_button

    def block_buttons(self, caller_button):
        """Block buttons except caller one."""
        buttons_to_block = [
            button for button in self.blockable_buttons
            if button and button.isEnabled() and button != caller_button
        ]
        for button in buttons_to_block:
            button.setEnabled(False)

    def unblock_buttons(self):
        """Unblock all buttons."""
        for button in self.blockable_buttons:
            if button:
                button.setEnabled(True)