diff --git a/mapadroid/ocr/pogoWindows.py b/mapadroid/ocr/pogoWindows.py index 8110792e3..c71dce746 100644 --- a/mapadroid/ocr/pogoWindows.py +++ b/mapadroid/ocr/pogoWindows.py @@ -618,3 +618,75 @@ def __screendetection_get_type_internal(self, image, return None return returntype, globaldict, width, height, diff + + def check_finished_quest(self, image, identifier): + return self.__thread_pool.apply_async(self.__check_finished_quest_internal, (image, identifier)).get() + + def __check_finished_quest_internal(self, image, identifier): + origin_logger = get_origin_logger(logger, origin=identifier) + results = {"breakthrough": [], "finished": [], "blocked": [], "ar": []} + globaldict: Optional[dict] = {} + diff: int = 1 + origin_logger.debug("__check_finished_quest_interal") + + try: + with Image.open(image) as frame_org: + width, height = frame_org.size + origin_logger.debug("Screensize: W:{} x H:{}", width, height) + + if width < 1080: + origin_logger.info('Resize screen ...') + frame_org = frame_org.resize([int(2 * s) for s in frame_org.size], Image.ANTIALIAS) + diff: int = 2 + + try: + globaldict = pytesseract.image_to_data(frame_org, output_type=Output.DICT, timeout=40, + config='--dpi 70') + except Exception as e: + origin_logger.error("Tesseract Error: {}. Exception: {}", globaldict, e) + globaldict = None + + origin_logger.debug("Screentext: {}", globaldict) + if globaldict is None or 'text' not in globaldict: + return results + + n_boxes = len(globaldict['text']) + for index in range(n_boxes): + # original screen coordinates + left = globaldict["left"][index] / diff + top = globaldict["top"][index] / diff + + if globaldict['text'][index] in ["REWARD!", "ABHOLEN!"]: + # get rgb values of a close "orange pixel" - the color differs between: + # the quest stack, normal finished quests, and quests blocked bc of the breakthrough + r, g, b = frame_org.getpixel((globaldict["left"][index], globaldict["top"][index] - 20 / diff)) + origin_logger.debug("Quest at {},{} has RGB: {}/{}/{}", left, top, r, g, b) + + # breakthrough reward is in the upper half of the screen + if top < height / 2: + origin_logger.debug("Found breakthrough reward at coords {},{}", left, top) + results["breakthrough"].append({'x': left, 'y': top}) + # blocked quest - breakthrough needs to be retrieved + elif 250 > r > 228 and g < 165 and b < 72: + origin_logger.debug("Found blocked quest at coords {},{}", left, top) + results["blocked"].append({'x': left, 'y': top}) + # finished quest - can be retrieved + elif 250 > r > 228 and b < 90: + origin_logger.debug("Found finished quest at coords {},{}", left, top) + results["finished"].append({'x': left, 'y': top}) + # quest stack - to be ignored + elif r > 249 and b < 90: + origin_logger.debug("Found stacked quest at coords {},{}", left, top) + + if globaldict['text'][index] in ["Scan", "scannen"]: + origin_logger.debug("Found AR quest at coords {},{}", left, top) + results["ar"].append({'x': left, 'y': top}) + + except (FileNotFoundError, ValueError) as e: + origin_logger.error("Failed opening image {} with exception {}", image, e) + return results + + # reverse sort by y so that clicking will start from the bottom - starting from the top causes entries to move + if results["finished"]: + results["finished"] = sorted(results["finished"], key=lambda k: k['y'], reverse=True) + return results diff --git a/mapadroid/worker/MITMBase.py b/mapadroid/worker/MITMBase.py index c116f2626..808f45dc8 100644 --- a/mapadroid/worker/MITMBase.py +++ b/mapadroid/worker/MITMBase.py @@ -349,14 +349,76 @@ def _get_route_manager_settings_and_distance_to_current_location(self): self.logger.debug('Moving {} meters to the next position', round(distance, 2)) return distance, routemanager_settings - def _clear_quests(self, delayadd, openmenu=True): + def _clear_quests(self, delayadd, openmenu=True, check_finished=False, looped=False): self.logger.debug('{_clear_quests} called') + + reached_main_menu = self._check_pogo_main_screen(10, True) + if not reached_main_menu: + if not self._restart_pogo(mitm_mapper=self._mitm_mapper): + # TODO: put in loop, count up for a reboot ;) + raise InternalStopWorkerException + if openmenu: x, y = self._resocalc.get_coords_quest_menu(self) self._communicator.click(int(x), int(y)) self.logger.debug("_clear_quests Open menu: {}, {}", int(x), int(y)) time.sleep(6 + int(delayadd)) + if check_finished: + x, y = self._resocalc.get_quest_listview(self) + self._communicator.click(int(x), int(y)) + self.logger.debug("_clear_quests Open field: {}, {}", int(x), int(y)) + time.sleep(4 + int(delayadd)) + + cleaned_count: int = 0 + if looped: + self.logger.info("Retry clearing finished quests after retrieving breakthrough reward") + else: + self.logger.info("Check for finished quests") + + questcheck = self._check_finished_quest(full_screen=True) + if not questcheck: + self.logger.warning("Quest check OCR failed. Move on.") + else: + if questcheck["blocked"]: + if not questcheck["breakthrough"] and not looped: + self.logger.warning("Found blocked quest but no breakthrough reward - likely flawed OCR" + "result. This can be ignored if not happening repeatedly.") + elif not looped: + self.logger.warning("Found a blocked quest - need to try to cleanup breakthrough!") + self._communicator.click(questcheck["breakthrough"][0]['x'], + questcheck["breakthrough"][0]['y']) + time.sleep(20) + self._communicator.back_button() + time.sleep(5) + self._clear_quests(delayadd, openmenu=False, check_finished=True, looped=True) + return + else: + self.logger.error("Unable to clean breakthrough - the reward pokemon needs to be caught!") + elif questcheck["breakthrough"]: + self.logger.warning("Breakthrough reward found - consider catching it soon!") + + if not questcheck["finished"]: + self.logger.info("Unable to find any finished quests.") + for quest in questcheck["finished"]: + cleaned_count += 1 + self.logger.info("Retrieving finished quest #{}", cleaned_count) + self._communicator.click(quest['x'], quest['y']) + time.sleep(15) + self._communicator.back_button() + time.sleep(5) + + # back button throws us to the map, return to the quest menu if more quests to be cleared + # otherwise just return without trying to click the close button + if len(questcheck["finished"]) > cleaned_count: + x, y = self._resocalc.get_coords_quest_menu(self) + self._communicator.click(int(x), int(y)) + self.logger.debug("_clear_quests Open menu again: {}, {}", int(x), int(y)) + time.sleep(6 + int(delayadd)) + else: + self.logger.success("Done clearing finished quests!") + return + x, y = self._resocalc.get_close_main_button_coords(self) self._communicator.click(int(x), int(y)) time.sleep(1.5) diff --git a/mapadroid/worker/WorkerBase.py b/mapadroid/worker/WorkerBase.py index f72e3868b..2cc6d2af7 100644 --- a/mapadroid/worker/WorkerBase.py +++ b/mapadroid/worker/WorkerBase.py @@ -843,6 +843,18 @@ def _get_trash_positions(self, full_screen=False): return trashes + def _check_finished_quest(self, full_screen=False): + self.logger.debug("_check_finished_quest: Check finished quest.") + if not self._take_screenshot(delay_before=self.get_devicesettings_value("post_screenshot_delay", 1)): + self.logger.debug("_check_finished_quest: Failed getting screenshot") + return None + + if os.path.isdir(self.get_screenshot_path()): + self.logger.error("_check_finished_quest: screenshot.png is not a file/corrupted") + return None + + return self._pogoWindowManager.check_finished_quest(self.get_screenshot_path(), self._origin) + def _take_screenshot(self, delay_after=0.0, delay_before=0.0, errorscreen: bool = False): self.logger.debug2("Taking screenshot...") time.sleep(delay_before) diff --git a/mapadroid/worker/WorkerQuests.py b/mapadroid/worker/WorkerQuests.py index 5a34faf34..3600508b1 100644 --- a/mapadroid/worker/WorkerQuests.py +++ b/mapadroid/worker/WorkerQuests.py @@ -37,6 +37,7 @@ class ClearThreadTasks(Enum): IDLE = 0 BOX = 1 QUEST = 2 + QUEST_WITH_FINISHED = 3 class PositionStopType(Enum): @@ -133,7 +134,7 @@ def _pre_work_loop(self): else: # initial cleanup old quests if not self._init: - self.clear_thread_task = ClearThreadTasks.QUEST + self.clear_thread_task = ClearThreadTasks.QUEST_WITH_FINISHED def _health_check(self): """ @@ -310,7 +311,11 @@ def _clear_thread(self): self.logger.info("Clearing quest") self._clear_quests(self._delay_add) self.clear_thread_task = ClearThreadTasks.IDLE - time.sleep(1) + elif self.clear_thread_task == ClearThreadTasks.QUEST_WITH_FINISHED and not self._level_mode: + self.logger.info("Clearing quests and checking for finished quests") + self._clear_quests(self._delay_add, check_finished=True) + self.clear_thread_task = ClearThreadTasks.IDLE + time.sleep(1) except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException, WebsocketWorkerConnectionClosedException): self.logger.error("Worker removed while clearing quest/box") @@ -717,7 +722,7 @@ def _handle_stop(self, timestamp: float): if not self._restart_pogo(mitm_mapper=self._mitm_mapper): # TODO: put in loop, count up for a reboot ;) raise InternalStopWorkerException - self.clear_thread_task = ClearThreadTasks.QUEST + self.clear_thread_task = ClearThreadTasks.QUEST_WITH_FINISHED self._clear_quest_counter = 0 break else: