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 __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 __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)
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.")
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()
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)
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.")
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)
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()
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)