diff --git a/module/handler/info_handler.py b/module/handler/info_handler.py index 09a3bdaf5..318ce63fc 100644 --- a/module/handler/info_handler.py +++ b/module/handler/info_handler.py @@ -571,4 +571,74 @@ class InfoHandler(ModuleBase): if manjuu_timer.reached(): break - return handled \ No newline at end of file + return handled + + def story_skip_with_option(self, drop=None, option=0): + """ + 2023.09.14 Story options changed with big white options in the middle, + Check STORY_SKIP_3 but click the original STORY_SKIP. + Returns: + [skipped, story_choice_clicked] + """ + if self.story_popup_timeout.started() and not self.story_popup_timeout.reached(): + if self.handle_popup_confirm('STORY_SKIP'): + self.story_popup_timeout = Timer(10) + self.interval_reset(STORY_SKIP_3) + self.interval_reset(STORY_LETTERS_ONLY) + return [True , False] + if self._is_story_black(): + if self.appear_then_click(STORY_LETTERS_ONLY, offset=(20, 20), interval=2): + self.story_popup_timeout.reset() + return [True , False] + if self._story_option_timer.reached() and self.appear(STORY_SKIP_3, offset=(20, 20), interval=0): + options = self._story_option_buttons_2() + options_count = len(options) + logger.attr('Story_options', options_count) + if not options_count: + self._story_option_record = 0 + self._story_option_confirm.reset() + elif options_count == self._story_option_record: + if self._story_option_confirm.reached(): + try: + select = options[option] + _op = option + except IndexError: + select = options[0] + _op = option + self.device.click(select) + self._story_option_timer.reset() + self.story_popup_timeout.reset() + self.interval_reset(STORY_SKIP_3) + self.interval_reset(STORY_LETTERS_ONLY) + self._story_option_record = 0 + self._story_option_confirm.reset() + return [True , _op] + else: + self._story_option_record = options_count + self._story_option_confirm.reset() + if self.appear(STORY_SKIP_3, offset=(20, 20), interval=2): + # Confirm it's story + # When story play speed is Very Fast, Alas clicked story skip but story disappeared + # This click will interrupt auto search + self.interval_reset([STORY_SKIP_3]) + if self._story_confirm.reached(): + if drop: + drop.handle_add(self, before=2) + if self.config.STORY_ALLOW_SKIP: + logger.info(f'{STORY_SKIP_3} -> {STORY_SKIP}') + self.device.click(STORY_SKIP) + else: + logger.info(f'{STORY_SKIP_3} -> {OS_CLICK_SAFE_AREA}') + self.device.click(OS_CLICK_SAFE_AREA) + self._story_confirm.reset() + self.story_popup_timeout.reset() + return [True , False] + else: + self.interval_clear(STORY_SKIP_3) + else: + self._story_confirm.reset() + if self.appear_then_click(STORY_CLOSE, offset=(10, 10), interval=2): + self.story_popup_timeout.reset() + return [True , False] + + return [False , False] diff --git a/module/os/fleet.py b/module/os/fleet.py index c39c40c3e..5d25e89f7 100644 --- a/module/os/fleet.py +++ b/module/os/fleet.py @@ -941,3 +941,166 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh): return False self.device.click(nearest) self._nearest_object_click_timer.reset() + + def _story_for_cross_zone_reset(self, drop=None): + """ + Wait until homo_loca stabled. + DETECTION_BACKEND must be 'homography'. + + Args: + drop (DropImage): + + Returns: + str: Things that fleet met on its way, + 'event', 'search', 'akashi', 'combat', + or their combinations like 'event_akashi', 'event_combat', + or an empty string '' if nothing met. + + Raises: + MapWalkError: If unable to goto such grid. + """ + logger.hr('Wait until walk stable') + record = None + enemy_searching_appear = False + self.device.screenshot_interval_set(0.35) + confirm_timer = Timer(1.5, count=4) + result = set() + # Record story history to clear click record + clicked_story = False + stuck_timer = Timer(20, count=5).start() + clicked_middle = 0 + for _ in self.loop(skip_first=False): + # Map event + + # If clicked Middle for twice, means it's a scanning device, which need to quit to save it. + if clicked_middle >1: + option = -1 + result.add ("siren_scanning_device") + else: + option = -2 + + event = self.story_skip_with_option(drop=drop, option=option) + if event[1] == -2: + clicked_middle += 1 + event = event[0] + if event: + confirm_timer.reset() + stuck_timer.reset() + result.add('event') + if event == 'story_skip': + clicked_story = True + elif event == 'map_get_items': + # story_skip -> map_get_items means abyssal progress reward is received + if clicked_story: + logger.info('Got items from story') + self.device.click_record_clear() + clicked_story = False + else: + # Handled other events, clear history + clicked_story = False + continue + if self.handle_retirement(): + confirm_timer.reset() + stuck_timer.reset() + continue + if self.handle_popup_confirm('WALK_UNTIL_STABLE'): + # Confirm to submit items, in siren scanning devices + confirm_timer.reset() + stuck_timer.reset() + continue + + # Accident click + if self.is_in_globe(): + self.os_globe_goto_map() + confirm_timer.reset() + stuck_timer.reset() + continue + if self.is_in_storage(): + self.storage_quit() + confirm_timer.reset() + stuck_timer.reset() + continue + if self.is_in_os_mission(): + self.os_mission_quit() + confirm_timer.reset() + stuck_timer.reset() + continue + if self.handle_os_game_tips(): + confirm_timer.reset() + stuck_timer.reset() + continue + if self.is_in_map_order(): + self.order_quit() + confirm_timer.reset() + stuck_timer.reset() + continue + + # Combat + if self.combat_appear(): + # Use ui_back() for testing, because there are too few abyssal loggers every month. + # self.ui_back(check_button=self.is_in_map) + self.combat(expected_end=self.is_in_map, fleet_index=self.fleet_show_index, save_get_items=drop) + confirm_timer.reset() + stuck_timer.reset() + result.add('event') + continue + + # Akashi shop + if self.appear(PORT_SUPPLY_CHECK, offset=(20, 20)): + self.interval_clear(PORT_SUPPLY_CHECK) + self.handle_akashi_supply_buy(CLICK_SAFE_AREA) + confirm_timer.reset() + stuck_timer.reset() + result.add('akashi') + continue + + # A game bug that AUTO_SEARCH_REWARD from the last cleared zone popups + if self.appear_then_click(AUTO_SEARCH_REWARD, offset=(50, 50), interval=3): + confirm_timer.reset() + stuck_timer.reset() + continue + + # Enemy searching + if not enemy_searching_appear and self.enemy_searching_appear(): + enemy_searching_appear = True + confirm_timer.reset() + stuck_timer.reset() + continue + else: + if enemy_searching_appear: + self.handle_enemy_flashing() + self.device.sleep(0.3) + logger.info('Enemy searching appeared.') + enemy_searching_appear = False + confirm_timer.reset() + stuck_timer.reset() + result.add('search') + if self.is_in_map(): + self.enemy_searching_color_initial() + + # Arrive + # Check colors, because screen goes black when something is unlocking. + # A direct use of IN_MAP, basically `self.is_in_map() and IN_MAP.match_template_color()` + if self.match_template_color(IN_MAP, offset=(200, 5)): + self.update_os() + current = self.view.backend.homo_loca + logger.attr('homo_loca', current) + # Max known distance is 4.48px, homo_loca between ( 56, 60) and ( 52, 58) + if record is None or (current is not None and np.linalg.norm(np.subtract(current, record)) < 5.5): + if confirm_timer.reached(): + break + else: + if stuck_timer.reached(): + logger.warning(f"homo_loca stuck at current view, try reset.") + if self.fleet_reset_view(): + stuck_timer.reset() + confirm_timer.reset() + record = current + else: + confirm_timer.reset() + stuck_timer.reset() + + result = '_'.join(result) + logger.info(f'Walk stabled, result: {result}') + self.device.screenshot_interval_set() + return result \ No newline at end of file diff --git a/module/os/map.py b/module/os/map.py index ee7844867..8d9d089bd 100644 --- a/module/os/map.py +++ b/module/os/map.py @@ -4,6 +4,7 @@ from sys import maxsize import inflection import scheduler_watcher +from encodings.punycode import selective_find from module.base.timer import Timer from module.combat_ui.assets import PAUSE from module.config.utils import get_os_reset_remain @@ -767,7 +768,13 @@ class OSMap(OSFleet, Map, GlobeCamera, StrategicSearchHandler): self._solved_map_event = set() self._solved_fleet_mechanism = False if self.config.full_config.OpsiCrossZoneScanningDeviceReset_Scheduler_Enable: - self.map_rescan() + if self.zone.hazard_level>=5: + self.clear_question_for_cross_zone_reset(drop=drop) + else: + self.clear_question(drop=drop) + self.map_rescan(rescan_mode=rescan, drop=drop) + self.config.task_call("OpsiCrossZoneScanningDeviceReset") + self.config.task_stop() else: if question: self.clear_question(drop=drop) @@ -956,3 +963,50 @@ class OSMap(OSFleet, Map, GlobeCamera, StrategicSearchHandler): logger.warning('Too many trial on map rescan, stop') self.fleet_set(self.config.OpsiFleet_Fleet) return False + + def clear_question_for_cross_zone_reset(self, drop): + """ + Clear nearly (and 3 grids from above) question marks on radar. + Try 3 times at max to avoid loop tries on 2 adjacent fleet mechanism. + + Args: + drop: + + Returns: + bool: If cleared + """ + logger.hr('Clear question', level=2) + for _ in range(3): + grid = self.radar.predict_question(self.device.image, in_port=self.zone.is_port) + if grid is None: + logger.info('No question mark above current fleet on this radar') + return False + + logger.info(f'Found question mark on {grid}') + self.handle_info_bar() + + self.update_os() + self.view.predict() + self.view.show() + + grid = self.convert_radar_to_local(grid) + self.device.click(grid) + with self.config.temporary(STORY_ALLOW_SKIP=False): + result = self._story_for_cross_zone_reset(drop=drop) + if 'akashi' in result: + self._solved_map_event.add('is_akashi') + return True + elif 'event' in result and grid.is_logging_tower: + self._solved_map_event.add('is_logging_tower') + return True + elif 'event' in result and (grid.is_scanning_device or "siren_scanning_device" in result): + self._solved_map_event.add('is_scanning_device') + self.os_auto_search_run_exit_combat(drop=drop) + return "is_scanning_device" + else: + logger.warning(f'Arrive question with unexpected result: {result}, expected: {grid.str}') + continue + + logger.warning('Failed to goto question mark after 5 trail, ' + 'this might be 2 adjacent fleet mechanism, stopped') + return False diff --git a/module/os_handler/map_event.py b/module/os_handler/map_event.py index 51bf64f13..df7adda99 100644 --- a/module/os_handler/map_event.py +++ b/module/os_handler/map_event.py @@ -273,3 +273,29 @@ class MapEventHandler(EnemySearchingHandler): changed = fleet_lock.set(state, main=self) return changed + + def handle_map_event_for_cross_zone_reset(self, drop=None, option=0): + """ + Args: + drop (DropImage): + option (int): + + Returns: + str: Event that handled + """ + if self.handle_map_get_items(drop=drop): + return 'map_get_items' + if self.handle_os_game_tips(): + return 'os_game_tips' + if self.handle_map_archives(drop=drop): + return 'map_archives' + if self.handle_guild_popup_cancel(): + return 'guild_popup_cancel' + if self.handle_ash_popup(): + return 'ash_popup' + if self.handle_urgent_commission(drop=drop): + return 'urgent_commission' + if self.story_skip_with_option(option=option): + return 'story_skip' + + return '' \ No newline at end of file