コード例 #1
0
class RewardTacticalClass(UI, InfoHandler):
    tactical_animation_timer = Timer(2, count=3)

    def _tactical_animation_running(self):
        """
        Returns:
            bool: If showing skill points increasing animation.
        """
        color_height = np.mean(self.device.image.crop((922, 0, 1036, 720)).convert('L'), axis=1)
        parameters = {'height': 200}
        peaks, _ = signal.find_peaks(color_height, **parameters)
        peaks = [y for y in peaks if y > 67 + 243]

        if not len(peaks):
            logger.warning('No student card found.')
        for y in peaks:
            student_area = (447, y - 243, 1244, y)
            area = area_offset((677, 172, 761, 183), student_area[0:2])
            # Normal: 160, In skill-increasing animation: 109
            if np.mean(get_color(self.device.image, area)) < 135:
                return True

        return False

    def _tactical_books_choose(self):
        """
        Choose tactical book according to config.
        """
        books = BookGroup([Book(self.device.image, button) for button in BOOKS_GRID.buttons()]).select(valid=True)
        logger.attr('Book_count', len(books))
        for index in range(1, 4):
            logger.info(f'Book_T{index}: {books.select(tier=index)}')
        if not books:
            logger.warning('No book found.')
            raise ScriptError('No book found.')

        if not time_range_active(self.config.TACTICAL_NIGHT_RANGE):
            tier = self.config.TACTICAL_BOOK_TIER
            exp = self.config.TACTICAL_EXP_FIRST
        else:
            tier = self.config.TACTICAL_BOOK_TIER_NIGHT
            exp = self.config.TACTICAL_EXP_FIRST_NIGHT
        book = books.choose(tier=tier, exp=exp)

        self.device.click(book.button)
        self.device.sleep((0.3, 0.5))

    def _tactical_class_receive(self, skip_first_screenshot=True):
        """Remember to make sure current page is page_reward before calls.

        Args:
            skip_first_screenshot (bool):

        Returns:
            bool: If rewarded.
        """
        if not self.appear(REWARD_2):
            logger.info('No tactical class reward.')
            return False

        logger.hr('Tactical class receive')
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.appear_then_click(REWARD_2, interval=1):
                continue
            if self.handle_popup_confirm():
                continue
            if self.handle_urgent_commission(save_get_items=False):
                # Only one button in the middle, when skill reach max level.
                continue
            if self.appear(TACTICAL_CLASS_CANCEL, offset=(30, 30), interval=2) \
                    and self.appear(TACTICAL_CLASS_START, offset=(30, 30)):
                self.device.sleep(0.3)
                self.device.screenshot()
                self._tactical_books_choose()
                self.device.click(TACTICAL_CLASS_START)
                self.interval_reset(TACTICAL_CLASS_CANCEL)
                continue

            # End
            if self.appear(TACTICAL_CHECK, offset=(20, 20)):
                self.ui_current = page_tactical
                if not self._tactical_animation_running():
                    if self.tactical_animation_timer.reached():
                        logger.info('Tactical reward end.')
                        break
                else:
                    self.tactical_animation_timer.reset()

        self.ui_goto(page_reward, skip_first_screenshot=True)
        return True

    def handle_tactical_class(self):
        """
        Returns:
            bool: If rewarded.
        """
        if not self.config.ENABLE_TACTICAL_REWARD:
            return False

        self._tactical_class_receive()

        return True
コード例 #2
0
    def _submarine_goto(self, location):
        """
        Move submarine to given location.

        Args:
            location (tuple, str, GridInfo): Destination.

        Returns:
            bool: If submarine moved.

        Pages:
            in: SUBMARINE_MOVE_CONFIRM
            out: SUBMARINE_MOVE_CONFIRM
        """
        location = location_ensure(location)
        moved = True
        while 1:
            self.in_sight(location, sight=self._walk_sight)
            self.focus_to_grid_center()
            grid = self.convert_global_to_local(location)
            grid.__str__ = location

            self.device.click(grid)
            arrived = False
            # Usually no need to wait
            arrive_timer = Timer(0.1, count=0)
            # If nothing happens, click again.
            walk_timeout = Timer(2, count=6).start()

            while 1:
                self.device.screenshot()
                self.view.update(image=self.device.image)

                # Arrive
                arrive_checker = grid.predict_submarine_move()
                if grid.predict_submarine() or (walk_timeout.reached() and grid.predict_fleet()):
                    arrive_checker = True
                    moved = False
                if arrive_checker:
                    if not arrive_timer.started():
                        logger.info(f'Arrive {location2node(location)}')
                    arrive_timer.start()
                    if not arrive_timer.reached():
                        continue
                    logger.info(f'Submarine arrive {location2node(location)} confirm.')
                    if not moved:
                        logger.info(f'Submarine already at {location2node(location)}')
                    arrived = True
                    break

                # End
                if walk_timeout.reached():
                    logger.warning('Walk timeout. Retrying.')
                    self.predict()
                    self.ensure_edge_insight(skip_first_update=False)
                    break

            # End
            if arrived:
                break

        return moved
コード例 #3
0
    def _tactical_class_receive(self, skip_first_screenshot=True):
        """
        Receive tactical rewards and fill books.

        Args:
            skip_first_screenshot (bool):

        Returns:
            bool: If rewarded.

        Pages:
            in: page_reward, TACTICAL_CLASS_START
            out: page_tactical
        """
        logger.hr('Tactical class receive', level=1)
        tactical_class_timout = Timer(10, count=10).start()
        tactical_animation_timer = Timer(2, count=3).start()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.appear_then_click(REWARD_2, interval=1):
                tactical_class_timout.reset()
                tactical_animation_timer.reset()
                continue
            if self.appear_then_click(REWARD_GOTO_TACTICAL,
                                      offset=(20, 20),
                                      interval=1):
                tactical_class_timout.reset()
                tactical_animation_timer.reset()
                continue
            if self.handle_popup_confirm('TACTICAL'):
                tactical_class_timout.reset()
                tactical_animation_timer.reset()
                continue
            if self.handle_urgent_commission():
                # Only one button in the middle, when skill reach max level.
                tactical_class_timout.reset()
                tactical_animation_timer.reset()
                continue
            if self.appear(TACTICAL_CLASS_CANCEL, offset=(30, 30), interval=2) \
                    and self.appear(TACTICAL_CLASS_START, offset=(30, 30)):
                self.device.sleep(0.3)
                self._tactical_books_choose()
                self.interval_reset(TACTICAL_CLASS_CANCEL)
                tactical_class_timout.reset()
                tactical_animation_timer.reset()
                continue

            # End
            if self.appear(TACTICAL_CHECK, offset=(20, 20)):
                self.ui_current = page_tactical
                if not self._tactical_animation_running():
                    if tactical_animation_timer.reached():
                        logger.info('Tactical reward end.')
                        break
                else:
                    tactical_animation_timer.reset()
            if tactical_class_timout.reached():
                logger.info('Tactical reward timeout.')
                break

        return True
コード例 #4
0
class InfoHandler(ModuleBase):
    """
    Class to handle all kinds of message.
    """
    """
    Info bar
    """
    def info_bar_count(self):
        if self.appear(INFO_BAR_3):
            return 3
        elif self.appear(INFO_BAR_2):
            return 2
        elif self.appear(INFO_BAR_1):
            return 1
        else:
            return 0

    def handle_info_bar(self):
        if self.info_bar_count():
            self.wait_until_disappear(INFO_BAR_1)
            return True
        else:
            return False

    def ensure_no_info_bar(self, timeout=0.6):
        timeout = Timer(timeout)
        timeout.start()
        while 1:
            self.device.screenshot()
            self.handle_info_bar()

            if timeout.reached():
                break

    """
    Popup info
    """
    _popup_offset = (3, 30)

    def handle_popup_confirm(self, name=''):
        if self.appear(POPUP_CANCEL, offset=self._popup_offset) \
                and self.appear(POPUP_CONFIRM, offset=self._popup_offset, interval=2):
            POPUP_CONFIRM.name = POPUP_CONFIRM.name + '_' + name
            self.device.click(POPUP_CONFIRM)
            POPUP_CONFIRM.name = POPUP_CONFIRM.name[:-len(name) - 1]
            return True
        else:
            return False

    def handle_popup_cancel(self, name=''):
        if self.appear(POPUP_CONFIRM, offset=self._popup_offset) \
                and self.appear(POPUP_CANCEL, offset=self._popup_offset, interval=2):
            POPUP_CANCEL.name = POPUP_CANCEL.name + '_' + name
            self.device.click(POPUP_CANCEL)
            POPUP_CANCEL.name = POPUP_CANCEL.name[:-len(name) - 1]
            return True
        else:
            return False

    def handle_urgent_commission(self, save_get_items=None):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if save_get_items is None:
            save_get_items = self.config.ENABLE_SAVE_GET_ITEMS

        appear = self.appear(GET_MISSION, offset=True, interval=2)
        if appear:
            logger.info('Get urgent commission')
            if save_get_items:
                self.handle_info_bar()
                self.device.save_screenshot('get_mission')
            self.device.click(GET_MISSION)
        return appear

    def handle_combat_low_emotion(self):
        if not self.config.IGNORE_LOW_EMOTION_WARN:
            return False

        return self.handle_popup_confirm('IGNORE_LOW_EMOTION')

    """
    Story
    """

    story_popup_timout = Timer(10, count=20)

    def story_skip(self):
        if self.story_popup_timout.started(
        ) and not self.story_popup_timout.reached():
            if self.handle_popup_confirm('STORY_SKIP'):
                self.story_popup_timout = Timer(10)
                self.interval_reset(STORY_SKIP)
                self.interval_reset(STORY_LETTERS_ONLY)
                return True
        if self.appear_then_click(STORY_SKIP, offset=True, interval=2):
            self.story_popup_timout.reset()
            return True
        if self.appear(STORY_LETTER_BLACK) and self.appear_then_click(
                STORY_LETTERS_ONLY, offset=True, interval=2):
            self.story_popup_timout.reset()
            return True
        if self.appear_then_click(STORY_CHOOSE, offset=True, interval=2):
            self.story_popup_timout.reset()
            return True
        if self.appear_then_click(STORY_CHOOSE_2, offset=True, interval=2):
            self.story_popup_timout.reset()
            return True

        return False

    def handle_story_skip(self):
        if not self.config.ENABLE_MAP_CLEAR_MODE:
            return False
        if self.config.ENABLE_FAST_FORWARD:
            return False

        return self.story_skip()

    def ensure_no_story(self, skip_first_screenshot=True):
        logger.info('Ensure no story')
        story_timer = Timer(5, count=10).start()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.story_skip():
                story_timer.reset()

            if story_timer.reached():
                break

    def handle_map_after_combat_story(self):
        if not self.config.MAP_HAS_MAP_STORY:
            return False

        self.ensure_no_story()
コード例 #5
0
    def _reward_mission_collect(self, interval=1):
        """
        Streamline handling of mission rewards for
        both 'all' and 'weekly' pages

        Args:
            interval (int): Configure the interval for
                            assets involved

        Returns:
            bool, if encountered at least 1 GET_ITEMS_*
        """
        # Reset any existing interval for the following assets
        [
            self.interval_clear(asset) for asset in [
                GET_ITEMS_1, GET_ITEMS_2, MISSION_MULTI, MISSION_SINGLE,
                GET_SHIP
            ]
        ]

        # Basic timers for certain scenarios
        exit_timer = Timer(2)
        click_timer = Timer(1)
        timeout = Timer(10)
        exit_timer.start()
        timeout.start()

        reward = False
        while 1:
            self.device.screenshot()

            for button in [GET_ITEMS_1, GET_ITEMS_2]:
                if self.appear_then_click(button,
                                          offset=(30, 30),
                                          interval=interval):
                    exit_timer.reset()
                    timeout.reset()
                    reward = True
                    continue

            for button in [MISSION_MULTI, MISSION_SINGLE]:
                if not click_timer.reached():
                    continue
                if self.appear(button, offset=(0, 200), interval=interval) \
                        and button.match_appear_on(self.device.image):
                    self.device.click(button)
                    exit_timer.reset()
                    click_timer.reset()
                    timeout.reset()
                    continue

            if not self.appear(MISSION_CHECK):
                if self.appear_then_click(GET_SHIP, interval=interval):
                    exit_timer.reset()
                    click_timer.reset()
                    timeout.reset()
                    continue

            if self.handle_mission_popup_ack():
                exit_timer.reset()
                click_timer.reset()
                timeout.reset()
                continue

            # Story
            if self.handle_vote_popup():
                exit_timer.reset()
                click_timer.reset()
                timeout.reset()
                continue
            if self.story_skip():
                exit_timer.reset()
                click_timer.reset()
                timeout.reset()
                continue

            if self.handle_popup_confirm('MISSION_REWARD'):
                exit_timer.reset()
                click_timer.reset()
                timeout.reset()
                continue

            # End
            if reward and exit_timer.reached():
                break
            if timeout.reached():
                logger.warning('Wait get items timeout.')
                break

        return reward
コード例 #6
0
class AutoSearchCombat(MapOperation, Combat):
    _auto_search_in_stage_timer = Timer(3, count=6)
    _auto_search_confirm_low_emotion = False
    auto_search_oil_limit_triggered = False

    def _handle_auto_search_menu_missing(self):
        """
        Sometimes game is bugged, auto search menu is not shown.
        After BOSS battle, it enters campaign directly.
        To handle this, if game in campaign for a certain time, it means auto search ends.

        Returns:
            bool: If triggered
        """
        if self.is_in_stage():
            if self._auto_search_in_stage_timer.reached():
                logger.info('Catch auto search menu missing')
                return True
        else:
            self._auto_search_in_stage_timer.reset()

        return False

    def auto_search_watch_fleet(self, checked=False):
        """
        Watch fleet index and ship level.

        Args:
            checked (bool): Watchers are only executed or logged once during fleet moving.
                            Set True to skip executing again.

        Returns:
            bool: If executed.
        """
        prev = self.fleet_current_index
        self.get_fleet_show_index()
        self.get_fleet_current_index()
        if self.fleet_current_index == prev:
            # Same as current, only print once
            if not checked:
                logger.info(
                    f'Fleet: {self.fleet_show_index}, fleet_current_index: {self.fleet_current_index}'
                )
                checked = True
                self.lv_get(after_battle=True)
        else:
            # Fleet changed
            logger.info(
                f'Fleet: {self.fleet_show_index}, fleet_current_index: {self.fleet_current_index}'
            )
            checked = True
            self.lv_get(after_battle=False)

        return checked

    def auto_search_watch_oil(self, checked=False):
        """
        Watch oil.
        This will set auto_search_oil_limit_triggered.
        """
        if not checked:
            oil = OCR_OIL.ocr(self.device.image)
            if oil == 0:
                logger.warning('Oil not found')
            else:
                if oil < self.config.StopCondition_OilLimit:
                    logger.info('Reach oil limit')
                    self.auto_search_oil_limit_triggered = True
                checked = True

        return checked

    def auto_search_moving(self, skip_first_screenshot=True):
        """
        Pages:
            in: map
            out: is_combat_loading()
        """
        logger.info('Auto search moving')
        self.device.stuck_record_clear()
        checked_fleet = False
        checked_oil = False
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.is_auto_search_running():
                checked_fleet = self.auto_search_watch_fleet(checked_fleet)
                checked_oil = self.auto_search_watch_oil(checked_oil)
            if self.handle_retirement():
                continue
            if self.handle_auto_search_map_option():
                continue
            if self.handle_combat_low_emotion():
                self._auto_search_confirm_low_emotion = True
                continue
            if self.handle_story_skip():
                continue
            if self.handle_map_cat_attack():
                continue
            if self.handle_vote_popup():
                continue

            # End
            if self.is_combat_loading():
                break
            if self.is_in_auto_search_menu(
            ) or self._handle_auto_search_menu_missing():
                raise CampaignEnd

    def auto_search_combat_execute(self, emotion_reduce, fleet_index):
        """
        Args:
            emotion_reduce (bool):
            fleet_index (int):

        Pages:
            in: is_combat_loading()
            out: combat status
        """
        logger.info('Auto search combat loading')
        self.device.screenshot_interval_set(
            self.config.Optimization_CombatScreenshotInterval)
        while 1:
            self.device.screenshot()

            if self.handle_combat_automation_confirm():
                continue
            if self.handle_story_skip():
                continue
            if self.handle_vote_popup():
                continue

            # End
            if self.is_in_auto_search_menu(
            ) or self._handle_auto_search_menu_missing():
                raise CampaignEnd
            if self.is_combat_executing():
                break

        logger.info('Auto Search combat execute')
        self.submarine_call_reset()
        submarine_mode = 'do_not_use'
        if self.config.Submarine_Fleet:
            submarine_mode = self.config.Submarine_Mode
        self.combat_auto_reset()
        self.combat_manual_reset()
        if emotion_reduce:
            self.emotion.reduce(fleet_index)
        auto = self.config.Fleet_Fleet1Mode if fleet_index == 1 else self.config.Fleet_Fleet2Mode

        while 1:
            self.device.screenshot()

            if self.handle_submarine_call(submarine_mode):
                continue
            if self.handle_combat_auto(auto):
                continue
            if self.handle_combat_manual(auto):
                continue
            if auto != 'combat_auto' and self.auto_mode_checked and self.is_combat_executing(
            ):
                if self.handle_combat_weapon_release():
                    continue
            if self.handle_popup_confirm('AUTO_SEARCH_COMBAT_EXECUTE'):
                continue
            if self.handle_story_skip():
                continue
            if self.handle_vote_popup():
                continue

            # End
            if self.is_in_auto_search_menu(
            ) or self._handle_auto_search_menu_missing():
                self.device.screenshot_interval_set(0)
                raise CampaignEnd
            if self.is_combat_executing():
                continue
            if self.appear(BATTLE_STATUS_S) or self.appear(BATTLE_STATUS_A) or self.appear(BATTLE_STATUS_B) \
                    or self.appear(EXP_INFO_S) or self.appear(EXP_INFO_A) or self.appear(EXP_INFO_B) \
                    or self.is_auto_search_running():
                self.device.screenshot_interval_set(0)
                break

    def auto_search_combat_status(self, skip_first_screenshot=True):
        """
        Pages:
            in: any
            out: is_auto_search_running()
        """
        logger.info('Auto Search combat status')
        exp_info = False  # This is for the white screen bug in game

        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            # End
            if self.is_auto_search_running():
                self._auto_search_confirm_low_emotion = False
                break
            if self.is_in_auto_search_menu(
            ) or self._handle_auto_search_menu_missing():
                raise CampaignEnd

            # Combat status
            if self.handle_get_ship():
                continue
            if self.handle_popup_confirm('AUTO_SEARCH_COMBAT_STATUS'):
                continue
            if self.handle_auto_search_map_option():
                self._auto_search_confirm_low_emotion = False
                continue
            if self.handle_urgent_commission():
                continue
            if self.handle_story_skip():
                continue
            if self.handle_guild_popup_cancel():
                continue
            if self.handle_vote_popup():
                continue

            # Handle low emotion combat
            # Combat status
            if self._auto_search_confirm_low_emotion:
                if not exp_info and self.handle_get_ship():
                    continue
                if self.handle_get_items():
                    continue
                if self.handle_battle_status():
                    continue
                if self.handle_popup_confirm('combat_status'):
                    continue
                if self.handle_exp_info():
                    exp_info = True
                    continue

    def auto_search_combat(self, emotion_reduce=None, fleet_index=1):
        """
        Execute a combat.

        Note that fleet index == 1 is mob fleet, 2 is boss fleet.
        It's not the fleet index in fleet preparation or auto search setting.
        """
        emotion_reduce = emotion_reduce if emotion_reduce is not None else self.config.Emotion_CalculateEmotion

        self.device.stuck_record_clear()
        self.auto_search_combat_execute(emotion_reduce=emotion_reduce,
                                        fleet_index=fleet_index)
        self.auto_search_combat_status()

        logger.info('Combat end.')
コード例 #7
0
class MapEventHandler(EnemySearchingHandler):
    def handle_map_get_items(self, interval=2):
        if self.is_in_map():
            return False

        if self.appear(GET_ITEMS_1, interval=interval) \
                or self.appear(GET_ITEMS_2, interval=interval) \
                or self.appear(GET_ITEMS_3, interval=interval):
            self.device.click(CLICK_SAFE_AREA)
            return True
        if self.appear(GET_ADAPTABILITY, interval=interval):
            self.device.click(CLICK_SAFE_AREA)
            return True
        if self.appear(GET_MEOWFFICER_ITEMS_1, interval=interval):
            self.device.click(CLICK_SAFE_AREA)
            return True
        if self.appear(GET_MEOWFFICER_ITEMS_2, interval=interval):
            self.device.click(CLICK_SAFE_AREA)
            return True

        return False

    def handle_map_archives(self):
        if self.appear(MAP_ARCHIVES, interval=5):
            self.device.click(CLICK_SAFE_AREA)
            return True
        if self.appear_then_click(MAP_WORLD, offset=(20, 20), interval=5):
            return True

        return False

    def handle_ash_popup(self):
        name = 'ASH'
        if self.appear(POPUP_CONFIRM, offset=self._popup_offset) \
                and self.appear(POPUP_CANCEL, offset=self._popup_offset, interval=2) \
                and self.image_color_count(ASH_POPUP_CHECK, color=(255, 93, 90), threshold=221, count=100):
            POPUP_CANCEL.name = POPUP_CANCEL.name + '_' + name
            self.device.click(POPUP_CANCEL)
            POPUP_CANCEL.name = POPUP_CANCEL.name[:-len(name) - 1]
            return True
        else:
            return False

    def handle_siren_platform(self):
        """
        Handle siren platform notice after entering map

        Returns:
            bool: If handled
        """
        if not self.handle_story_skip():
            return False

        logger.info('Handle siren platform')
        timeout = Timer(self.MAP_ENEMY_SEARCHING_TIMEOUT_SECOND).start()
        appeared = False
        while 1:
            self.device.screenshot()
            if self.is_in_map():
                timeout.start()
            else:
                timeout.reset()

            if self.handle_story_skip():
                timeout.reset()
                continue

            # End
            if self.enemy_searching_appear():
                appeared = True
            else:
                if appeared:
                    self.handle_enemy_flashing()
                    self.device.sleep(1)
                    logger.info('Enemy searching appeared.')
                    break
                self.enemy_searching_color_initial()
            if timeout.reached():
                logger.info('Enemy searching timeout.')
                break

        return True

    def handle_map_event(self):
        """
        Returns:
            bool: If clicked to handle any map event.
        """
        if self.handle_map_get_items():
            return True
        if self.handle_map_archives():
            return True
        if self.handle_guild_popup_cancel():
            return True
        if self.handle_ash_popup():
            return True
        if self.handle_urgent_commission(save_get_items=False):
            return True
        if self.handle_story_skip():
            return True

        return False

    _os_in_map_confirm_timer = Timer(1.5, count=3)

    def handle_os_in_map(self):
        """
        Returns:
            bool: If is in map and confirmed.
        """
        if self.is_in_map():
            if self._os_in_map_confirm_timer.reached():
                return True
            else:
                return False
        else:
            self._os_in_map_confirm_timer.reset()
            return False

    def ensure_no_map_event(self):
        self._os_in_map_confirm_timer.reset()

        while 1:
            self.device.screenshot()

            if self.handle_map_event():
                continue

            # End
            if self.handle_os_in_map():
                break

    def os_auto_search_quit(self):
        confirm_timer = Timer(1, count=2).start()
        while 1:
            self.device.screenshot()

            if self.appear_then_click(AUTO_SEARCH_REWARD, offset=(20, 20), interval=1):
                confirm_timer.reset()
                continue
            if self.handle_map_event():
                confirm_timer.reset()
                continue

            # End
            if self.is_in_map():
                if confirm_timer.reached():
                    break
            else:
                confirm_timer.reset()

    def handle_os_auto_search_map_option(self):
        """
        Returns:
            bool: If clicked.
        """
        if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 50)) \
                and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image) \
                and self.info_bar_count() >= 2:
            raise CampaignEnd
        if self.appear(AUTO_SEARCH_REWARD, offset=(20, 50)):
            self.os_auto_search_quit()
            raise CampaignEnd
        if self.appear(AUTO_SEARCH_OS_MAP_OPTION_OFF, offset=(5, 50), interval=3) \
                and AUTO_SEARCH_OS_MAP_OPTION_OFF.match_appear_on(self.device.image):
            self.device.click(AUTO_SEARCH_OS_MAP_OPTION_OFF)
            return True
コード例 #8
0
    def enter_map(self, button, mode='normal', skip_first_screenshot=True):
        """Enter a campaign.

        Args:
            button: Campaign to enter.
            mode (str): 'normal' or 'hard' or 'cd'
            skip_first_screenshot (bool):
        """
        logger.hr('Enter map')
        campaign_timer = Timer(5)
        map_timer = Timer(5)
        fleet_timer = Timer(5)
        campaign_click = 0
        map_click = 0
        fleet_click = 0
        checked_in_map = False
        self.stage_entrance = button

        with self.stat.new(genre=self.config.campaign_name,
                           method=self.config.DropRecord_CombatRecord) as drop:
            while 1:
                if skip_first_screenshot:
                    skip_first_screenshot = False
                else:
                    self.device.screenshot()

                # Check errors
                if campaign_click > 5:
                    logger.critical(
                        f"Failed to enter {button}, too many click on {button}"
                    )
                    logger.critical(
                        "Possible reason #1: You haven't reached the commander level to unlock this stage."
                    )
                    raise RequestHumanTakeover
                if fleet_click > 5:
                    logger.critical(
                        f"Failed to enter {button}, too many click on FLEET_PREPARATION"
                    )
                    logger.critical(
                        "Possible reason #1: "
                        "Your fleets haven't satisfied the stat restrictions of this stage."
                    )
                    logger.critical(
                        "Possible reason #2: "
                        "This stage can only be farmed once a day, "
                        "but it's the second time that you are entering")
                    raise RequestHumanTakeover

                # Already in map
                if not checked_in_map and self.is_in_map():
                    logger.info('Already in map, skip enter_map.')
                    return False
                else:
                    checked_in_map = True

                # Map preparation
                if map_timer.reached() and self.handle_map_preparation():
                    self.map_get_info()
                    self.handle_fast_forward()
                    self.handle_auto_search()
                    if self.triggered_map_stop():
                        self.enter_map_cancel()
                        self.handle_map_stop()
                        raise ScriptEnd(
                            f'Reach condition: {self.config.StopCondition_MapAchievement}'
                        )
                    self.device.click(MAP_PREPARATION)
                    map_click += 1
                    map_timer.reset()
                    campaign_timer.reset()
                    continue

                # Fleet preparation
                if fleet_timer.reached() and self.appear(FLEET_PREPARATION,
                                                         offset=(20, 20)):
                    if mode == 'normal' or mode == 'hard':
                        self.handle_2x_book_setting(mode='prep')
                        self.fleet_preparation()
                        self.handle_auto_submarine_call_disable()
                        self.handle_auto_search_setting()
                        self.map_fleet_checked = True
                    self.device.click(FLEET_PREPARATION)
                    fleet_click += 1
                    fleet_timer.reset()
                    campaign_timer.reset()
                    continue

                # Auto search continue
                if self.handle_auto_search_continue():
                    campaign_timer.reset()
                    continue

                # Retire
                if self.handle_retirement():
                    continue

                # Use Data Key
                if self.handle_use_data_key():
                    continue

                # Emotion
                if self.handle_combat_low_emotion():
                    continue

                # Urgent commission
                if self.handle_urgent_commission(drop=drop):
                    continue

                # 2X book popup
                if self.handle_2x_book_popup():
                    continue

                # Story skip
                if self.handle_story_skip():
                    campaign_timer.reset()
                    continue

                # Enter campaign
                if campaign_timer.reached() and self.appear_then_click(button):
                    campaign_click += 1
                    campaign_timer.reset()
                    continue

                # End
                if self.map_is_auto_search:
                    if self.is_auto_search_running():
                        break
                else:
                    if self.handle_in_map_with_enemy_searching():
                        # self.handle_map_after_combat_story()
                        break

        return True
コード例 #9
0
    def set(self,
            main,
            left=None,
            right=None,
            upper=None,
            bottom=None,
            skip_first_screenshot=True):
        """
        Set nav bar from 1 direction.

        Args:
            main (ModuleBase):
            left (int): Index of nav item counted from left. Start from 1.
            right (int): Index of nav item counted from right. Start from 1.
            upper (int): Index of nav item counted from upper. Start from 1.
            bottom (int): Index of nav item counted from bottom. Start from 1.
            skip_first_screenshot (bool):

        Returns:
            bool: If success
        """
        if left is None and right is None and upper is None and bottom is None:
            logger.warning(
                'Invalid index to set, must set an index from 1 direction')
            return False
        text = ''
        if left is None and upper is not None:
            left = upper
        if right is None and bottom is not None:
            right = bottom
        for k in ['left', 'right', 'upper', 'bottom']:
            if locals().get(k, None) is not None:
                text += f'{k}={locals().get(k, None)} '
        logger.info(f'{self.name} set to {text.strip()}')

        interval = Timer(2, count=4)
        timeout = Timer(10, count=20).start()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                main.device.screenshot()

            if timeout.reached():
                logger.warning(f'{self.name} failed to set {text.strip()}')
                return False

            active, minimum, maximum = self.get_info(main=main)
            logger.info(
                f'Nav item active: {active} from range ({minimum}, {maximum})')
            # if active is None:
            #     continue
            index = minimum + left - 1 if left is not None else maximum - right + 1
            if not minimum <= index <= maximum:
                logger.warning(
                    f'Index to set ({index}) is not within the nav items that appears ({minimum}, {maximum})'
                )
                continue

            # End
            if active == index:
                return True

            if interval.reached():
                main.device.click(self.grids.buttons[index])
                main.device.sleep((0.1, 0.2))
                interval.reset()
コード例 #10
0
    def ui_goto(self,
                destination,
                offset=(20, 20),
                confirm_wait=0,
                skip_first_screenshot=True):
        """
        Args:
            destination (Page):
            offset:
            confirm_wait:
            skip_first_screenshot:
        """
        # Reset connection
        for page in self.ui_pages:
            page.parent = None

        # Create connection
        visited = [destination]
        visited = set(visited)
        while 1:
            new = visited.copy()
            for page in visited:
                for link in self.ui_pages:
                    if link in visited:
                        continue
                    if page in link.links:
                        link.parent = page
                        new.add(link)
            if len(new) == len(visited):
                break
            visited = new

        logger.hr(f'UI goto {destination}')
        confirm_timer = Timer(confirm_wait,
                              count=int(confirm_wait // 0.5)).start()
        while 1:
            GOTO_MAIN.clear_offset()
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            # Destination page
            if self.appear(destination.check_button, offset=offset):
                if confirm_timer.reached():
                    break
            else:
                confirm_timer.reset()

            # Other pages
            for page in visited:
                if page.parent is None or page.check_button is None:
                    continue
                if self.appear(page.check_button, offset=offset, interval=5):
                    self.device.click(page.links[page.parent])
                    confirm_timer.reset()
                    break

            # Additional
            if self.ui_additional():
                continue

        # Reset connection
        for page in self.ui_pages:
            page.parent = None
コード例 #11
0
class MapOperation(MysteryHandler, FleetPreparation, Retirement,
                   FastForwardHandler):
    map_cat_attack_timer = Timer(2)
    map_clear_percentage_prev = -1
    map_clear_percentage_timer = Timer(0.3, count=1)

    # Fleet that shows on screen.
    fleet_show_index = 1
    # Note that this is different from get_fleet_current_index()
    # In fleet_current_index, 1 means mob fleet, 2 means boss fleet.
    fleet_current_index = 1

    def get_fleet_show_index(self):
        """
        Get the fleet that shows on screen.

        Returns:
            int: 1 or 2

        Pages:
            in: in_map
        """
        if self.appear(FLEET_NUM_1, offset=(20, 20)):
            self.fleet_show_index = 1
            return 1
        elif self.appear(FLEET_NUM_2, offset=(20, 20)):
            self.fleet_show_index = 2
            return 2
        else:
            logger.warning('Unknown fleet current index, use 1 by default')
            self.fleet_show_index = 1
            return 1

    def get_fleet_current_index(self):
        """
        Returns:
            int: 1 or 2
        """
        if self.fleets_reversed:
            self.fleet_current_index = 3 - self.fleet_show_index
            return self.fleet_current_index
        else:
            self.fleet_current_index = self.fleet_show_index
            return self.fleet_current_index

    def fleet_set(self, index=None, skip_first_screenshot=True):
        """
        Args:
            index (int): Target fleet_current_index
            skip_first_screenshot (bool):

        Returns:
            bool: If switched.
        """
        logger.info(f'Fleet set to {index}')
        timeout = Timer(5, count=10).start()
        count = 0
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if timeout.reached():
                logger.warning(
                    'Fleet set timeout, assume current fleet is correct')
                break

            if self.handle_story_skip():
                timeout.reset()
                continue
            if self.handle_in_stage():
                timeout.reset()
                continue

            self.get_fleet_show_index()
            self.get_fleet_current_index()
            logger.info(
                f'Fleet: {self.fleet_show_index}, fleet_current_index: {self.fleet_current_index}'
            )
            if self.fleet_current_index == index:
                break
            elif self.appear_then_click(SWITCH_OVER):
                count += 1
                self.device.sleep((1, 1.5))
                timeout.reset()
                continue
            else:
                logger.warning('SWITCH_OVER not found')
                continue

        return count > 0

    def enter_map(self, button, mode='normal', skip_first_screenshot=True):
        """Enter a campaign.

        Args:
            button: Campaign to enter.
            mode (str): 'normal' or 'hard' or 'cd'
            skip_first_screenshot (bool):
        """
        logger.hr('Enter map')
        campaign_timer = Timer(5)
        map_timer = Timer(5)
        fleet_timer = Timer(5)
        campaign_click = 0
        map_click = 0
        fleet_click = 0
        checked_in_map = False
        self.stage_entrance = button

        with self.stat.new(genre=self.config.campaign_name,
                           method=self.config.DropRecord_CombatRecord) as drop:
            while 1:
                if skip_first_screenshot:
                    skip_first_screenshot = False
                else:
                    self.device.screenshot()

                # Check errors
                if campaign_click > 5:
                    logger.critical(
                        f"Failed to enter {button}, too many click on {button}"
                    )
                    logger.critical(
                        "Possible reason #1: You haven't reached the commander level to unlock this stage."
                    )
                    raise RequestHumanTakeover
                if fleet_click > 5:
                    logger.critical(
                        f"Failed to enter {button}, too many click on FLEET_PREPARATION"
                    )
                    logger.critical(
                        "Possible reason #1: "
                        "Your fleets haven't satisfied the stat restrictions of this stage."
                    )
                    logger.critical(
                        "Possible reason #2: "
                        "This stage can only be farmed once a day, "
                        "but it's the second time that you are entering")
                    raise RequestHumanTakeover

                # Already in map
                if not checked_in_map and self.is_in_map():
                    logger.info('Already in map, skip enter_map.')
                    return False
                else:
                    checked_in_map = True

                # Map preparation
                if map_timer.reached() and self.handle_map_preparation():
                    self.map_get_info()
                    self.handle_fast_forward()
                    self.handle_auto_search()
                    if self.triggered_map_stop():
                        self.enter_map_cancel()
                        self.handle_map_stop()
                        raise ScriptEnd(
                            f'Reach condition: {self.config.StopCondition_MapAchievement}'
                        )
                    self.device.click(MAP_PREPARATION)
                    map_click += 1
                    map_timer.reset()
                    campaign_timer.reset()
                    continue

                # Fleet preparation
                if fleet_timer.reached() and self.appear(FLEET_PREPARATION,
                                                         offset=(20, 20)):
                    if mode == 'normal' or mode == 'hard':
                        self.handle_2x_book_setting(mode='prep')
                        self.fleet_preparation()
                        self.handle_auto_submarine_call_disable()
                        self.handle_auto_search_setting()
                        self.map_fleet_checked = True
                    self.device.click(FLEET_PREPARATION)
                    fleet_click += 1
                    fleet_timer.reset()
                    campaign_timer.reset()
                    continue

                # Auto search continue
                if self.handle_auto_search_continue():
                    campaign_timer.reset()
                    continue

                # Retire
                if self.handle_retirement():
                    continue

                # Use Data Key
                if self.handle_use_data_key():
                    continue

                # Emotion
                if self.handle_combat_low_emotion():
                    continue

                # Urgent commission
                if self.handle_urgent_commission(drop=drop):
                    continue

                # 2X book popup
                if self.handle_2x_book_popup():
                    continue

                # Story skip
                if self.handle_story_skip():
                    campaign_timer.reset()
                    continue

                # Enter campaign
                if campaign_timer.reached() and self.appear_then_click(button):
                    campaign_click += 1
                    campaign_timer.reset()
                    continue

                # End
                if self.map_is_auto_search:
                    if self.is_auto_search_running():
                        break
                else:
                    if self.handle_in_map_with_enemy_searching():
                        # self.handle_map_after_combat_story()
                        break

        return True

    def enter_map_cancel(self, skip_first_screenshot=True):
        logger.hr('Enter map cancel')
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.appear(MAP_PREPARATION, offset=(20, 20)) or self.appear(
                    FLEET_PREPARATION, offset=(20, 20)):
                self.device.click(MAP_PREPARATION_CANCEL)
                continue

            if self.is_in_stage():
                break

        return True

    def handle_map_preparation(self):
        """
        Returns:
            bool: If MAP_PREPARATION and tha animation of map information finished
        """
        if not self.appear(MAP_PREPARATION, offset=(20, 20)):
            self.map_clear_percentage_prev = -1
            self.map_clear_percentage_timer.reset()
            return False

        percent = self.get_map_clear_percentage()
        logger.attr('Map_clear_percentage', percent)
        # Comment this because percentage starts from 100% and increase from 0% to actual value
        # if percent > 0.95:
        #     # map clear percentage 100%, exit directly
        #     return True
        if abs(percent - self.map_clear_percentage_prev) < 0.02:
            self.map_clear_percentage_prev = percent
            if self.map_clear_percentage_timer.reached():
                return True
            else:
                return False
        else:
            self.map_clear_percentage_prev = percent
            self.map_clear_percentage_timer.reset()
            return False

    def withdraw(self, skip_first_screenshot=True):
        """
        Withdraw campaign.
        """
        logger.hr('Map withdraw')
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.handle_popup_confirm('WITHDRAW'):
                continue
            if self.appear_then_click(WITHDRAW, interval=5):
                continue
            if self.handle_auto_search_exit():
                continue

            # End
            if self.handle_in_stage():
                raise CampaignEnd('Withdraw')

    def handle_map_cat_attack(self):
        """
        Click to skip the animation when cat attacks.
        """
        if not self.map_cat_attack_timer.reached():
            return False
        if self.image_color_count(MAP_CAT_ATTACK,
                                  color=(255, 231, 123),
                                  threshold=221,
                                  count=100):
            logger.info('Skip map cat attack')
            self.device.click(MAP_CAT_ATTACK)
            self.map_cat_attack_timer.reset()
            return True
        if not self.map_is_clear_mode:
            # Threat: Med has 106 pixels count, MAP_CAT_ATTACK_MIRROR has 290.
            if self.image_color_count(MAP_CAT_ATTACK_MIRROR,
                                      color=(255, 231, 123),
                                      threshold=221,
                                      count=200):
                logger.info('Skip map being attack')
                self.device.click(MAP_CAT_ATTACK)
                self.map_cat_attack_timer.reset()
                return True

        return False

    @property
    def fleets_reversed(self):
        if not self.config.FLEET_2:
            return False
        return self.config.Fleet_FleetOrder in [
            'fleet1_boss_fleet2_mob', 'fleet1_standby_fleet2_all'
        ]

    def handle_fleet_reverse(self):
        """
        The game chooses the fleet with a smaller index to be the first fleet,
        no matter what we choose in fleet preparation.

        After the update of auto-search, the game no longer ignore user settings.

        Returns:
            bool: Fleet changed
        """
        if not self.map_is_hard_mode \
                and self.config.Fleet_FleetOrder in ['fleet1_boss_fleet2_mob', 'fleet1_standby_fleet2_all']:
            logger.warning(
                f"You shouldn't use a reversed fleet order ({self.config.Fleet_FleetOrder}) in normal mode."
            )
            logger.warning(
                'Please reverse your Fleet 1 and Fleet 2, '
                'use "fleet1_mob_fleet2_boss" or "fleet1_all_fleet2_standby"')
            # raise RequestHumanTakeover

        if not self.fleets_reversed:
            return False

        return self.fleet_set(index=2)
コード例 #12
0
    def wait_until_walk_stable(self,
                               confirm_timer=None,
                               skip_first_screenshot=False,
                               walk_out_of_step=True,
                               drop=None):
        """
        Wait until homo_loca stabled.
        DETECTION_BACKEND must be 'homography'.

        Args:
            confirm_timer (Timer):
            skip_first_screenshot (bool):
            walk_out_of_step (bool): If catch walk_out_of_step error.
                Default to True, use False in abyssal zones.
            drop (DropImage):

        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)
        if confirm_timer is None:
            confirm_timer = Timer(0.8, count=2)

        confirm_timer.reset()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            # Map event
            if self.handle_map_event(drop=drop):
                confirm_timer.reset()
                continue
            if self.handle_retirement():
                confirm_timer.reset()
                continue
            if self.handle_walk_out_of_step():
                if walk_out_of_step:
                    raise MapWalkError('walk_out_of_step')
                else:
                    continue

            # Enemy searching
            if not enemy_searching_appear and self.enemy_searching_appear():
                enemy_searching_appear = True
                confirm_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
                if self.is_in_map():
                    self.enemy_searching_color_initial()

            # 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()
                continue

            # Arrive
            # Check colors, because screen goes black when something is unlocking.
            if self.is_in_map() and IN_MAP.match_appear_on(self.device.image):
                self.update_os()
                current = self.view.backend.homo_loca
                logger.attr('homo_loca', current)
                if record is None or (current is not None and np.linalg.norm(
                        np.subtract(current, record)) < 3):
                    if confirm_timer.reached():
                        break
                else:
                    confirm_timer.reset()
                record = current
            else:
                confirm_timer.reset()

        logger.info('Walk stabled')
        self.device.screenshot_interval_set()
コード例 #13
0
class Combat(HPBalancer, EnemySearchingHandler, Retirement, SubmarineCall, CombatAuto, CombatManual):
    _automation_set_timer = Timer(1)
    _emotion: Emotion
    battle_status_click_interval = 0

    @property
    def emotion(self):
        if not hasattr(self, '_emotion'):
            self._emotion = Emotion(config=self.config)
        return self._emotion

    def combat_appear(self):
        """
        Returns:
            bool: If enter combat.
        """
        if self.config.ENABLE_MAP_FLEET_LOCK and not self.is_in_map():
            if self.is_combat_loading():
                return True

        if self.appear(BATTLE_PREPARATION):
            return True
        if self.appear(BATTLE_PREPARATION_WITH_OVERLAY) and self.handle_combat_automation_confirm():
            return True

        return False

    def map_offensive(self):
        while 1:
            self.device.screenshot()

            if self.appear_then_click(MAP_OFFENSIVE, interval=1):
                continue
            if self.handle_combat_low_emotion():
                continue
            if self.handle_retirement():
                continue

            # Break
            if self.combat_appear():
                break

    def is_combat_loading(self):
        """
        Returns:
            bool:
        """
        left = color_bar_percentage(self.device.image, area=LOADING_BAR.area, prev_color=(99, 150, 255))
        right = color_bar_percentage(self.device.image, area=LOADING_BAR.area, prev_color=(225, 225, 225), reverse=True)
        if 0.15 < left < 0.95 and right > 0.15 and left + right <= 1.2:
            logger.attr('Loading', f'{int(left * 100)}%({int(right * 100)}%)')
            return True

        return False

    def is_combat_executing(self):
        """
        Returns:
            bool:
        """
        return self.appear(PAUSE) and np.max(self.device.image.crop(PAUSE_DOUBLE_CHECK.area)) < 153

    def handle_combat_automation_confirm(self):
        if self.appear(AUTOMATION_CONFIRM_CHECK, interval=1):
            self.appear_then_click(AUTOMATION_CONFIRM, offset=True)
            return True

        return False

    def combat_preparation(self, balance_hp=False, emotion_reduce=False, auto=True, fleet_index=1):
        """
        Args:
            balance_hp (bool):
            emotion_reduce (bool):
            auto (bool):
            fleet_index (int):
        """
        logger.info('Combat preparation.')

        if emotion_reduce:
            self.emotion.wait(fleet=fleet_index)
        if balance_hp:
            self.hp_balance()

        while 1:
            self.device.screenshot()

            if self.appear(BATTLE_PREPARATION):
                if self.handle_combat_automation_set(auto=auto):
                    continue
            if self.handle_retirement():
                if self.config.ENABLE_HP_BALANCE:
                    self.wait_until_appear(BATTLE_PREPARATION)
                    # When re-entering battle_preparation page, the emergency icon is active by default, even if
                    # nothing to use. After a short animation, everything shows as usual.
                    self.device.sleep(0.5)  # Wait animation.
                    continue
            if self.handle_combat_low_emotion():
                continue
            if self.handle_emergency_repair_use():
                continue
            if self.appear_then_click(BATTLE_PREPARATION, interval=2):
                continue
            if self.handle_combat_automation_confirm():
                continue
            if self.handle_story_skip():
                continue

            # End
            if self.is_combat_executing():
                if emotion_reduce:
                    self.emotion.reduce(fleet_index)
                break

    def handle_combat_automation_set(self, auto):
        """
        Args:
            auto (bool): If use auto.

        Returns:
            bool:
        """
        if not self._automation_set_timer.reached():
            return False

        if self.appear(AUTOMATION_ON):
            logger.info('[Automation] ON')
            if not auto:
                self.device.click(AUTOMATION_SWITCH)
                self.device.sleep(1)
                self._automation_set_timer.reset()
                return True

        if self.appear(AUTOMATION_OFF):
            logger.info('[Automation] OFF')
            if auto:
                self.device.click(AUTOMATION_SWITCH)
                self.device.sleep(1)
                self._automation_set_timer.reset()
                return True

        if self.appear_then_click(AUTOMATION_CONFIRM, offset=True):
            self._automation_set_timer.reset()
            return True

        return False

    def handle_emergency_repair_use(self):
        if not self.config.ENABLE_HP_BALANCE:
            return False
        if self.appear_then_click(EMERGENCY_REPAIR_CONFIRM, offset=True):
            self.device.sleep(0.5)  # Animation: hp increase and emergency_repair amount decrease.
            return True
        if self.appear(BATTLE_PREPARATION) and self.appear(EMERGENCY_REPAIR_AVAILABLE):
            logger.info('EMERGENCY_REPAIR_AVAILABLE')
            if not len(self.hp):
                return False
            if np.min(np.array(self.hp)[np.array(self.hp) > 0.001]) < self.config.EMERGENCY_REPAIR_SINGLE_THRESHOLD \
                    or np.max(self.hp[:3]) < self.config.EMERGENCY_REPAIR_HOLE_THRESHOLD \
                    or np.max(self.hp[3:]) < self.config.EMERGENCY_REPAIR_HOLE_THRESHOLD:
                logger.info('Use emergency repair')
                self.device.click(EMERGENCY_REPAIR_AVAILABLE)
                return True

        return False

    def combat_execute(self, auto=True, call_submarine_at_boss=False, save_get_items=False):
        """
        Args:
            auto (bool):
            call_submarine_at_boss (bool):
            save_get_items (bool)
        """
        logger.info('Combat execute')
        self.submarine_call_reset()
        self.combat_auto_reset()
        self.combat_manual_reset()
        confirm_timer = Timer(10)
        confirm_timer.start()
        self.device.screenshot_interval_set(self.config.COMBAT_SCREENSHOT_INTERVAL)

        while 1:
            self.device.screenshot()

            if not confirm_timer.reached() and self.appear_then_click(AUTOMATION_CONFIRM, offset=True):
                continue

            if self.handle_story_skip():
                continue
            if self.handle_combat_auto():
                continue
            if self.handle_combat_manual():
                continue
            if not auto and self.is_combat_executing():
                if self.handle_combat_weapon_release():
                    continue
            if call_submarine_at_boss:
                pass
            else:
                if self.handle_submarine_call():
                    continue

            # End
            if self.handle_battle_status(save_get_items=save_get_items):
                self.device.screenshot_interval_set(0)
                break

    def handle_battle_status(self, save_get_items=False):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if self.is_combat_executing():
            return False
        if self.appear_then_click(BATTLE_STATUS_S, screenshot=save_get_items, genre='status', interval=self.battle_status_click_interval):
            if not save_get_items:
                self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(BATTLE_STATUS_A, screenshot=save_get_items, genre='status', interval=self.battle_status_click_interval):
            logger.warning('Battle status: A')
            if not save_get_items:
                self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(BATTLE_STATUS_B, screenshot=save_get_items, genre='status', interval=self.battle_status_click_interval):
            logger.warning('Battle Status B')
            if not save_get_items:
                self.device.sleep((0.25, 0.5))
            return True

        return False

    def handle_get_items(self, save_get_items=False):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if self.appear_then_click(GET_ITEMS_1, screenshot=save_get_items, genre='get_items', offset=5,
                                  interval=self.battle_status_click_interval):
            self.interval_reset(BATTLE_STATUS_S)
            self.interval_reset(BATTLE_STATUS_A)
            self.interval_reset(BATTLE_STATUS_B)
            return True
        if self.appear_then_click(GET_ITEMS_2, screenshot=save_get_items, genre='get_items', offset=5,
                                  interval=self.battle_status_click_interval):
            self.interval_reset(BATTLE_STATUS_S)
            self.interval_reset(BATTLE_STATUS_A)
            self.interval_reset(BATTLE_STATUS_B)
            return True

        return False

    def handle_exp_info(self):
        """
        Returns:
            bool:
        """
        if self.appear_then_click(EXP_INFO_S):
            self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(EXP_INFO_A):
            self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(EXP_INFO_B):
            self.device.sleep((0.25, 0.5))
            return True

        return False

    def handle_get_ship(self, save_get_items=False):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if self.appear_then_click(GET_SHIP, screenshot=save_get_items, genre='get_ship'):
            return True

        return False

    def combat_status(self, save_get_items=False, expected_end=None):
        """
        Args:
            save_get_items (bool):
            expected_end (str): with_searching, no_searching, in_stage.
        """
        logger.info('Combat status')
        logger.attr('expected_end', expected_end)
        exp_info = False  # This is for the white screen bug in game
        while 1:
            self.device.screenshot()

            # Combat status
            if not exp_info and self.handle_get_ship(save_get_items=save_get_items):
                continue
            if self.handle_get_items(save_get_items=save_get_items):
                continue
            if self.handle_battle_status(save_get_items=save_get_items):
                continue
            if self.handle_popup_confirm():
                continue
            if self.handle_exp_info():
                exp_info = True
                continue
            if self.handle_urgent_commission(save_get_items=save_get_items):
                continue
            if self.handle_story_skip():
                continue

            # End
            if self.handle_in_stage():
                break
            if expected_end is None:
                if self.handle_in_map_with_enemy_searching():
                    break
            if isinstance(expected_end, str):
                if expected_end == 'in_stage' and self.handle_in_stage():
                    break
                if expected_end == 'with_searching' and self.handle_in_map_with_enemy_searching():
                    break
                if expected_end == 'no_searching' and self.handle_in_map_no_enemy_searching():
                    break
                if expected_end == 'in_ui' and self.appear(BACK_ARROW, offset=(20, 20)):
                    break
            if callable(expected_end):
                if expected_end():
                    break

    def combat(self, balance_hp=None, emotion_reduce=None, func=None, call_submarine_at_boss=None, save_get_items=None,
               expected_end=None, fleet_index=1):
        """
        Execute a combat.
        """
        balance_hp = balance_hp if balance_hp is not None else self.config.ENABLE_HP_BALANCE
        emotion_reduce = emotion_reduce if emotion_reduce is not None else self.config.ENABLE_EMOTION_REDUCE
        auto = self.config.COMBAT_AUTO_MODE == 'combat_auto'
        call_submarine_at_boss = call_submarine_at_boss if call_submarine_at_boss is not None else self.config.SUBMARINE_CALL_AT_BOSS
        save_get_items = save_get_items if save_get_items is not None else self.config.ENABLE_SAVE_GET_ITEMS
        self.battle_status_click_interval = 3 if save_get_items else 0

        # if not hasattr(self, 'emotion'):
        #     self.emotion = Emotion(config=self.config)

        self.combat_preparation(
            balance_hp=balance_hp, emotion_reduce=emotion_reduce, auto=auto, fleet_index=fleet_index)
        self.combat_execute(
            auto=auto, call_submarine_at_boss=call_submarine_at_boss, save_get_items=save_get_items)
        self.combat_status(
            save_get_items=save_get_items, expected_end=expected_end)
        self.handle_map_after_combat_story()
コード例 #14
0
class Combat(HPBalancer, EnemySearchingHandler, Retirement, SubmarineCall,
             CombatAuto, CombatManual):
    _automation_set_timer = Timer(1)
    _emotion: Emotion
    battle_status_click_interval = 0

    @property
    def emotion(self):
        if not hasattr(self, '_emotion'):
            self._emotion = Emotion(config=self.config)
        return self._emotion

    def combat_appear(self):
        """
        Returns:
            bool: If enter combat.
        """
        if self.config.ENABLE_MAP_FLEET_LOCK and not self.is_in_map():
            if self.is_combat_loading():
                return True

        if self.appear(BATTLE_PREPARATION):
            return True
        if self.appear(BATTLE_PREPARATION_WITH_OVERLAY
                       ) and self.handle_combat_automation_confirm():
            return True

        return False

    def map_offensive(self):
        while 1:
            self.device.screenshot()

            if self.appear_then_click(MAP_OFFENSIVE, interval=1):
                continue
            if self.handle_combat_low_emotion():
                continue
            if self.handle_retirement():
                continue

            # Break
            if self.combat_appear():
                break

    def is_combat_loading(self):
        """
        Returns:
            bool:
        """
        similarity, location = TEMPLATE_COMBAT_LOADING.match_result(
            self.device.image.crop((0, 620, 1280, 720)))
        if similarity > 0.85:
            loading = (location[0] + 38 - LOADING_BAR.area[0]) / (
                LOADING_BAR.area[2] - LOADING_BAR.area[0])
            logger.attr('Loading', f'{int(loading * 100)}%')
            return True
        else:
            return False

    def is_combat_executing(self):
        """
        Returns:
            bool:
        """
        return self.appear(PAUSE) and np.max(
            self.device.image.crop(PAUSE_DOUBLE_CHECK.area)) < 153

    def ensure_combat_oil_loaded(self):
        self.wait_until_stable(COMBAT_OIL_LOADING)

    def handle_combat_automation_confirm(self):
        if self.appear(AUTOMATION_CONFIRM_CHECK, interval=1):
            self.appear_then_click(AUTOMATION_CONFIRM, offset=True)
            return True

        return False

    def combat_preparation(self,
                           balance_hp=False,
                           emotion_reduce=False,
                           auto='combat_auto',
                           fleet_index=1):
        """
        Args:
            balance_hp (bool):
            emotion_reduce (bool):
            auto (str):
            fleet_index (int):
        """
        logger.info('Combat preparation.')

        if emotion_reduce:
            self.emotion.wait(fleet=fleet_index)
        if balance_hp:
            self.hp_balance()

        while 1:
            self.device.screenshot()

            if self.appear(BATTLE_PREPARATION):
                if self.handle_combat_automation_set(
                        auto=auto == 'combat_auto'):
                    continue
            if self.handle_retirement():
                continue
            if self.handle_combat_low_emotion():
                continue
            if balance_hp and self.handle_emergency_repair_use():
                continue
            if self.appear_then_click(BATTLE_PREPARATION, interval=2):
                continue
            if self.handle_combat_automation_confirm():
                continue
            if self.handle_story_skip():
                continue

            # End
            if self.is_combat_executing():
                if emotion_reduce:
                    self.emotion.reduce(fleet_index)
                break

    def handle_combat_automation_set(self, auto):
        """
        Args:
            auto (bool): If use auto.

        Returns:
            bool:
        """
        if not self._automation_set_timer.reached():
            return False

        if self.appear(AUTOMATION_ON):
            logger.info('[Automation] ON')
            if not auto:
                self.device.click(AUTOMATION_SWITCH)
                self.device.sleep(1)
                self._automation_set_timer.reset()
                return True

        if self.appear(AUTOMATION_OFF):
            logger.info('[Automation] OFF')
            if auto:
                self.device.click(AUTOMATION_SWITCH)
                self.device.sleep(1)
                self._automation_set_timer.reset()
                return True

        if self.appear_then_click(AUTOMATION_CONFIRM, offset=True):
            self._automation_set_timer.reset()
            return True

        return False

    def handle_emergency_repair_use(self):
        if self.appear_then_click(EMERGENCY_REPAIR_CONFIRM, offset=True):
            return True
        if self.appear(BATTLE_PREPARATION) and self.appear(
                EMERGENCY_REPAIR_AVAILABLE):
            # When entering battle_preparation page (or after emergency repairing), the emergency icon is active by default,
            # even if nothing to use. After a short animation, everything shows as usual. Using fleet power number as a
            # stable checker. First wait for it to be non-zero, then wait for it to be stable.
            self.wait_until_disappear(MAIN_FLEET_POWER_ZERO, offset=(20, 20))
            stable_checker = Button(area=MAIN_FLEET_POWER_ZERO.area,
                                    color=(),
                                    button=MAIN_FLEET_POWER_ZERO.button,
                                    name='STABLE_CHECKER')
            self.wait_until_stable(stable_checker)
            if not self.appear(EMERGENCY_REPAIR_AVAILABLE):
                return False
            logger.info('EMERGENCY_REPAIR_AVAILABLE')
            if not len(self.hp):
                return False
            if np.min(np.array(self.hp)[np.array(self.hp) > 0.001]) < self.config.EMERGENCY_REPAIR_SINGLE_THRESHOLD \
                    or np.max(self.hp[:3]) < self.config.EMERGENCY_REPAIR_HOLE_THRESHOLD \
                    or np.max(self.hp[3:]) < self.config.EMERGENCY_REPAIR_HOLE_THRESHOLD:
                logger.info('Use emergency repair')
                self.device.click(EMERGENCY_REPAIR_AVAILABLE)
                return True

        return False

    def combat_execute(self,
                       auto='combat_auto',
                       call_submarine_at_boss=False,
                       save_get_items=False):
        """
        Args:
            auto (str): Combat auto mode.
            call_submarine_at_boss (bool):
            save_get_items (bool)
        """
        logger.info('Combat execute')
        self.submarine_call_reset()
        self.combat_auto_reset()
        self.combat_manual_reset()
        confirm_timer = Timer(10)
        confirm_timer.start()
        self.device.screenshot_interval_set(
            self.config.COMBAT_SCREENSHOT_INTERVAL)

        while 1:
            self.device.screenshot()

            if not confirm_timer.reached() and self.appear_then_click(
                    AUTOMATION_CONFIRM, offset=True):
                continue

            if self.handle_story_skip():
                continue
            if self.handle_combat_auto(auto):
                continue
            if self.handle_combat_manual(auto):
                continue
            if auto != 'combat_auto' and self.auto_mode_checked and self.is_combat_executing(
            ):
                if self.handle_combat_weapon_release():
                    continue
            if call_submarine_at_boss:
                pass
            else:
                if self.handle_submarine_call():
                    continue

            # End
            if self.handle_battle_status(
                    save_get_items=save_get_items) or self.handle_get_items(
                        save_get_items=save_get_items):
                self.device.screenshot_interval_set(0)
                break

    def handle_battle_status(self, save_get_items=False):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if self.is_combat_executing():
            return False
        if self.appear_then_click(BATTLE_STATUS_S,
                                  screenshot=save_get_items,
                                  genre='status',
                                  interval=self.battle_status_click_interval):
            if not save_get_items:
                self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(BATTLE_STATUS_A,
                                  screenshot=save_get_items,
                                  genre='status',
                                  interval=self.battle_status_click_interval):
            logger.warning('Battle status: A')
            if not save_get_items:
                self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(BATTLE_STATUS_B,
                                  screenshot=save_get_items,
                                  genre='status',
                                  interval=self.battle_status_click_interval):
            logger.warning('Battle Status B')
            if not save_get_items:
                self.device.sleep((0.25, 0.5))
            return True

        return False

    def handle_get_items(self, save_get_items=False):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if self.appear_then_click(GET_ITEMS_1,
                                  screenshot=save_get_items,
                                  genre='get_items',
                                  offset=5,
                                  interval=self.battle_status_click_interval):
            self.interval_reset(BATTLE_STATUS_S)
            self.interval_reset(BATTLE_STATUS_A)
            self.interval_reset(BATTLE_STATUS_B)
            return True
        if self.appear_then_click(GET_ITEMS_2,
                                  screenshot=save_get_items,
                                  genre='get_items',
                                  offset=5,
                                  interval=self.battle_status_click_interval):
            self.interval_reset(BATTLE_STATUS_S)
            self.interval_reset(BATTLE_STATUS_A)
            self.interval_reset(BATTLE_STATUS_B)
            return True

        return False

    def handle_exp_info(self):
        """
        Returns:
            bool:
        """
        if self.is_combat_executing():
            return False
        if self.appear_then_click(EXP_INFO_S):
            self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(EXP_INFO_A):
            self.device.sleep((0.25, 0.5))
            return True
        if self.appear_then_click(EXP_INFO_B):
            self.device.sleep((0.25, 0.5))
            return True

        return False

    def handle_get_ship(self, save_get_items=False):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if self.appear_then_click(GET_SHIP,
                                  screenshot=save_get_items,
                                  genre='get_ship'):
            return True

        return False

    def combat_status(self, save_get_items=False, expected_end=None):
        """
        Args:
            save_get_items (bool):
            expected_end (str): with_searching, no_searching, in_stage.
        """
        logger.info('Combat status')
        logger.attr(
            'expected_end',
            expected_end.__name__ if callable(expected_end) else expected_end)
        exp_info = False  # This is for the white screen bug in game
        while 1:
            self.device.screenshot()

            # Combat status
            if not exp_info and self.handle_get_ship(
                    save_get_items=save_get_items):
                continue
            if self.handle_get_items(save_get_items=save_get_items):
                continue
            if self.handle_battle_status(save_get_items=save_get_items):
                continue
            if self.handle_popup_confirm():
                continue
            if self.handle_exp_info():
                exp_info = True
                continue
            if self.handle_urgent_commission(save_get_items=save_get_items):
                continue
            if self.handle_story_skip():
                continue

            # End
            if self.handle_in_stage():
                break
            if expected_end is None:
                if self.handle_in_map_with_enemy_searching():
                    break
            if isinstance(expected_end, str):
                if expected_end == 'in_stage' and self.handle_in_stage():
                    break
                if expected_end == 'with_searching' and self.handle_in_map_with_enemy_searching(
                ):
                    break
                if expected_end == 'no_searching' and self.handle_in_map_no_enemy_searching(
                ):
                    break
                if expected_end == 'in_ui' and self.appear(BACK_ARROW,
                                                           offset=(20, 20)):
                    break
            if callable(expected_end):
                if expected_end():
                    break

    def combat(self,
               balance_hp=None,
               emotion_reduce=None,
               auto_mode=None,
               call_submarine_at_boss=None,
               save_get_items=None,
               expected_end=None,
               fleet_index=1):
        """
        Execute a combat.
        """
        balance_hp = balance_hp if balance_hp is not None else self.config.ENABLE_HP_BALANCE
        emotion_reduce = emotion_reduce if emotion_reduce is not None else self.config.ENABLE_EMOTION_REDUCE
        if auto_mode is None:
            auto_mode = self.config.FLEET_1_AUTO_MODE if fleet_index == 1 else self.config.FLEET_2_AUTO_MODE
        call_submarine_at_boss = call_submarine_at_boss if call_submarine_at_boss is not None else self.config.SUBMARINE_CALL_AT_BOSS
        save_get_items = save_get_items if save_get_items is not None else self.config.ENABLE_SAVE_GET_ITEMS
        self.battle_status_click_interval = 7 if save_get_items else 0

        # if not hasattr(self, 'emotion'):
        #     self.emotion = Emotion(config=self.config)

        self.combat_preparation(balance_hp=balance_hp,
                                emotion_reduce=emotion_reduce,
                                auto=auto_mode,
                                fleet_index=fleet_index)
        self.combat_execute(auto=auto_mode,
                            call_submarine_at_boss=call_submarine_at_boss,
                            save_get_items=save_get_items)
        self.combat_status(save_get_items=save_get_items,
                           expected_end=expected_end)
        self.handle_map_after_combat_story()
コード例 #15
0
ファイル: ui.py プロジェクト: wingtofree/AzurLaneAutoScript
class UI(InfoHandler):
    ui_pages = [page_main, page_campaign, page_fleet, page_exercise, page_daily, page_event, page_sp, page_mission,
                page_raid, page_reward, page_reshmenu, page_research, page_dormmenu, page_meowfficer]
    ui_pages_all = [page_main, page_campaign, page_fleet, page_exercise, page_daily, page_event, page_sp, page_mission,
                    page_raid, page_commission, page_event_list, page_tactical, page_reward, page_unknown,
                    page_reshmenu, page_research, page_dormmenu, page_meowfficer]
    ui_current: Page

    def ui_page_appear(self, page):
        """
        Args:
            page (Page):
        """
        return self.appear(page.check_button, offset=(20, 20))

    def ensure_button_execute(self, button, offset=0):
        if isinstance(button, Button) and self.appear(button, offset=offset):
            return True
        elif callable(button) and button():
            return True
        else:
            return False

    def ui_click(self, click_button, check_button, appear_button=None, additional=None, confirm_wait=1,
                 offset=(20, 20), retry_wait=10, skip_first_screenshot=False):
        """
        Args:
            click_button (Button):
            check_button (Button, callable):
            appear_button (Button, callable):
            additional (callable):
            confirm_wait (int, float):
            offset (bool, int, tuple):
            retry_wait (int, float):
            skip_first_screenshot (bool):
        """
        logger.hr('UI click')
        if appear_button is None:
            appear_button = click_button

        click_timer = Timer(retry_wait, count=retry_wait // 0.5)
        confirm_wait = confirm_wait if additional is not None else 0
        confirm_timer = Timer(confirm_wait, count=confirm_wait // 0.5).start()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if (isinstance(check_button, Button) and self.appear(check_button, offset=offset)) \
                    or (callable(check_button) and check_button()):
                if confirm_timer.reached():
                    break
            else:
                confirm_timer.reset()

            if click_timer.reached():
                if (isinstance(appear_button, Button) and self.appear(appear_button, offset=offset)) \
                        or (callable(appear_button) and appear_button()):
                    self.device.click(click_button)
                    click_timer.reset()
                    continue

            if additional is not None:
                if additional():
                    continue

    def ui_get_current_page(self):
        self.device.screenshot()
        for page in self.ui_pages:
            if self.ui_page_appear(page=page):
                logger.attr('UI', page.name)
                self.ui_current = page
                return page

        logger.info('Unknown ui page')
        if self.appear_then_click(GOTO_MAIN, offset=(20, 20)):
            logger.info('Goto page_main')
            self.ui_current = page_unknown
            self.ui_goto(page_main, skip_first_screenshot=True)

        if hasattr(self, 'ui_current'):
            logger.warning(f'Unrecognized ui_current, using previous: {self.ui_current}')
        else:
            logger.info('Unable to goto page_main')
            logger.warning('Starting from current page is not supported')
            logger.warning(f'Supported page: {[str(page) for page in self.ui_pages]}')
            logger.warning(f'Supported page: Any page with a "HOME" button on the upper-right')
            if not self.device.app_is_running():
                raise GameNotRunningError('Game not running')
            else:
                exit(1)

    def ui_goto(self, destination, skip_first_screenshot=False):
        """
        Args:
            destination (Page):
            skip_first_screenshot (bool):
        """
        for page in self.ui_pages_all:
            page.parent = None
        # Iter
        visited = [self.ui_current]
        visited = set(visited)
        while 1:
            new = visited.copy()
            for page in visited:
                for link in page.links.keys():
                    if link in visited:
                        continue
                    link.parent = page
                    new.add(link)
            if len(new) == len(visited):
                break
            visited = new

        # Find path
        if destination.parent is None:
            return []
        route = [destination]
        while 1:
            destination = destination.parent
            if destination is not None:
                route.append(destination)
            else:
                break
            if len(route) > 30:
                logger.warning('UI route too long')
                logger.warning(str(route))
                exit(1)

        route.reverse()
        if len(route) < 2:
            logger.warning('No page route found.')
        logger.attr('UI route', ' - '.join([p.name for p in route]))

        # Click
        for p1, p2 in zip(route[:-1], route[1:]):
            additional = f'ui_additional_{str(p2)}'
            self.ui_click(
                click_button=p1.links[p2],
                check_button=p2.check_button,
                additional=self.__getattribute__(additional) if hasattr(self, additional) else None,
                offset=(20, 20),
                skip_first_screenshot=skip_first_screenshot)
            self.ui_current = p2
            skip_first_screenshot = True

        # Reset
        for page in visited:
            page.parent = None

    def ui_ensure(self, destination):
        """
        Args:
            destination (Page):
        """
        logger.hr('UI ensure')
        self.ui_get_current_page()
        if self.ui_current == destination:
            logger.info('Already at %s' % destination)
            return False
        else:
            logger.info('Goto %s' % destination)
            self.ui_goto(destination)
            return True

    def ui_goto_main(self):
        return self.ui_ensure(destination=page_main)

    def handle_stage_icon_spawn(self):
        self.device.sleep((1, 1.2))
        self.device.screenshot()

    def ui_weigh_anchor(self):
        if self.ui_ensure(destination=page_campaign):
            self.handle_stage_icon_spawn()
            return True
        else:
            return False

    def ui_goto_event(self):
        if self.ui_ensure(destination=page_event):
            self.handle_stage_icon_spawn()
            return True
        else:
            return False

    def ui_goto_sp(self):
        if self.ui_ensure(destination=page_sp):
            self.handle_stage_icon_spawn()
            return True
        else:
            return False

    def ui_ensure_index(self, index, letter, next_button, prev_button, skip_first_screenshot=False, fast=True,
                        interval=(0.2, 0.3), step_sleep=(0.2, 0.3), finish_sleep=(0.5, 0.8)):
        """
        Args:
            index (int):
            letter (Ocr, callable): OCR button.
            next_button (Button):
            prev_button (Button):
            skip_first_screenshot (bool):
            fast (bool): Default true. False when index is not continuous.
            interval (tuple, int, float): Seconds between two click.
            step_sleep (tuple, int, float): Seconds between two step.
            finish_sleep (tuple, int, float): Second to wait when arrive.
        """
        logger.hr('UI ensure index')
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if isinstance(letter, Ocr):
                current = letter.ocr(self.device.image)
            else:
                current = letter(self.device.image)

            logger.attr('Index', current)
            diff = index - current
            if diff == 0:
                break

            button = next_button if diff > 0 else prev_button
            if fast:
                self.device.multi_click(button, n=abs(diff), interval=interval)
            else:
                self.device.click(button)
            self.device.sleep(step_sleep)

        self.device.sleep(finish_sleep)

    def ui_back(self, check_button, appear_button=None, offset=(20, 20), retry_wait=10, skip_first_screenshot=False):
        return self.ui_click(click_button=BACK_ARROW, check_button=check_button, appear_button=appear_button,
                             offset=offset, retry_wait=retry_wait, skip_first_screenshot=skip_first_screenshot)

    def ui_additional_page_main(self):
        # Research popup, lost connection popup
        if self.handle_popup_confirm('PAGE_MAIN'):
            return True

        # Daily reset
        if self.appear_then_click(LOGIN_ANNOUNCE, offset=(30, 30), interval=5):
            return True
        if self.appear_then_click(GET_ITEMS_1, offset=(30, 30), interval=5):
            return True
        if self.appear_then_click(GET_SHIP, interval=5):
            return True
        if self.appear_then_click(LOGIN_RETURN_SIGN, offset=(30, 30), interval=5):
            return True
        if self.appear_then_click(GOTO_MAIN, offset=(30, 30), interval=5):
            return True

        return False

    _ui_additional_reward_goto_main_timer = Timer(5, count=5)

    def ui_additional_page_reward(self):
        # Research popup, lost connection popup
        if self.handle_popup_confirm('PAGE_REWARD'):
            return True

        # Daily reset
        if self.appear_then_click(LOGIN_ANNOUNCE, offset=(30, 30), interval=5):
            return True
        if self.appear_then_click(GET_ITEMS_1, offset=(30, 30), interval=5):
            return True
        if self.appear_then_click(GET_SHIP, interval=5):
            return True
        if self.appear_then_click(LOGIN_RETURN_SIGN, offset=(30, 30), interval=5):
            return True
        if self.appear(EVENT_LIST_CHECK, offset=(30, 30), interval=5) \
                or self.appear(RESHMENU_CHECK, offset=(30, 30), interval=5) \
                or self.appear(RESEARCH_CHECK, offset=(30, 30), interval=5):
            self.device.click(GOTO_MAIN)
            self._ui_additional_reward_goto_main_timer.reset()
            return True
        if not self._ui_additional_reward_goto_main_timer.reached():
            if self.appear(MAIN_CHECK, offset=(30, 30), interval=5):
                self.device.click(MAIN_GOTO_REWARD)
                return True

        return False
コード例 #16
0
class Device(Screenshot, Control, AppControl):
    _screen_size_checked = False
    stuck_record = set()
    stuck_timer = Timer(60, count=60).start()
    stuck_timer_long = Timer(300, count=300).start()
    stuck_long_wait_list = ['BATTLE_STATUS_S', 'PAUSE']

    def handle_night_commission(self, hour=21, threshold=30):
        """
        Args:
            hour (int): Hour that night commission refresh.
            threshold (int): Seconds around refresh time.

        Returns:
            bool: If handled.
        """
        update = self.config.get_server_last_update(since=(hour, ))
        now = datetime.now().time()
        if now < (update - timedelta(seconds=threshold)).time():
            return False
        if now > (update + timedelta(seconds=threshold)).time():
            return False

        if GET_MISSION.match(self.image, offset=True):
            logger.info('Night commission appear.')
            self.click(GET_MISSION)
            return True

        return False

    def screenshot(self):
        """
        Returns:
            PIL.Image.Image:
        """
        self.stuck_record_check()
        super().screenshot()
        if self.handle_night_commission():
            super().screenshot()

        if not self._screen_size_checked:
            self.check_screen()
            self._screen_size_checked = True

        return self.image

    def click(self, button, record_check=True):
        self.stuck_record_clear()
        return super().click(button, record_check=record_check)

    def check_screen(self):
        """
        Screen size must be 1280x720.
        Take a screenshot before call.
        """
        # Check screen size
        width, height = self.image.size
        logger.attr('Screen_size', f'{width}x{height}')
        if width == 1280 and height == 720:
            return True
        else:
            logger.warning(f'Not supported screen size: {width}x{height}')
            logger.warning('Alas requires 1280x720')
            logger.hr('Script end')
            exit(1)

        # Check screen color
        # May get a pure black screenshot on some emulators.
        color = get_color(self.image, area=(0, 0, 1280, 720))
        if sum(color) < 1:
            logger.warning('Received a pure black screenshot')
            logger.warning(f'Color: {color}')
            exit(1)

    def stuck_record_add(self, button):
        self.stuck_record.add(str(button))

    def stuck_record_clear(self):
        self.stuck_record = set()
        self.stuck_timer.reset()
        self.stuck_timer_long.reset()

    def stuck_record_check(self):
        reached = self.stuck_timer.reached()
        reached_long = self.stuck_timer_long.reached()

        if not reached:
            return False
        if not reached_long:
            for button in self.stuck_long_wait_list:
                if button in self.stuck_record:
                    return False

        logger.warning('Wait too long')
        logger.warning(f'Waiting for {self.stuck_record}')
        self.stuck_record_clear()

        if self.config.ENABLE_GAME_STUCK_HANDLER:
            raise GameStuckError(f'Wait too long')
コード例 #17
0
    def _guild_operations_ensure(self, skip_first_screenshot=True):
        """
        Ensure guild operation is loaded
        After entering guild operation, background loaded first, then dispatch/boss
        """
        logger.attr('Guild master/official',
                    self.config.GuildOperation_SelectNewOperation)
        confirm_timer = Timer(1.5, count=3).start()
        click_count = 0
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            # End
            if click_count > 5:
                # Info bar showing `none4302`.
                # Probably because guild operation has been started by another guild officer already.
                # Enter guild page again should fix the issue.
                logger.warning(
                    'Unable to start/join guild operation, '
                    'probably because guild operation has been started by another guild officer already'
                )
                raise GameBugError('Unable to start/join guild operation')

            if self._handle_guild_operations_start():
                confirm_timer.reset()
                continue
            if self.appear(GUILD_OPERATIONS_JOIN, interval=3):
                if self.image_color_count(GUILD_OPERATIONS_MONTHLY_COUNT,
                                          color=(255, 93, 90),
                                          threshold=221,
                                          count=20):
                    logger.info(
                        'Unable to join operation, no more monthly attempts left'
                    )
                    self.device.click(GUILD_OPERATIONS_CLICK_SAFE_AREA)
                else:
                    current, remain, total = GUILD_OPERATIONS_PROGRESS.ocr(
                        self.device.image)
                    threshold = total * self.config.GuildOperation_JoinThreshold
                    if current <= threshold:
                        logger.info(
                            'Joining Operation, current progress less than '
                            f'threshold ({threshold:.2f})')
                        self.device.click(GUILD_OPERATIONS_JOIN)
                    else:
                        logger.info(
                            'Refrain from joining operation, current progress exceeds '
                            f'threshold ({threshold:.2f})')
                        self.device.click(GUILD_OPERATIONS_CLICK_SAFE_AREA)
                confirm_timer.reset()
                continue
            if self.handle_popup_confirm('JOIN_OPERATION'):
                click_count += 1
                confirm_timer.reset()
                continue
            if self.handle_popup_single('FLEET_UPDATED'):
                logger.info(
                    'Fleet composition altered, may still be dispatch-able. However '
                    'fellow guild members have updated their support line up. '
                    'Suggestion: Enable Boss Recommend')
                confirm_timer.reset()
                continue

            # End
            if self.appear(GUILD_BOSS_ENTER) or self.appear(
                    GUILD_OPERATIONS_ACTIVE_CHECK, offset=(20, 20)):
                if not self.info_bar_count() and confirm_timer.reached():
                    break
コード例 #18
0
    def wait_until_walk_stable(self,
                               confirm_timer=None,
                               skip_first_screenshot=False,
                               walk_out_of_step=True,
                               drop=None):
        """
        Wait until homo_loca stabled.
        DETECTION_BACKEND must be 'homography'.

        Args:
            confirm_timer (Timer):
            skip_first_screenshot (bool):
            walk_out_of_step (bool): If catch walk_out_of_step error.
                Default to True, use False in abyssal zones.
            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)
        if confirm_timer is None:
            confirm_timer = Timer(0.8, count=2)
        result = set()

        confirm_timer.reset()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            # Map event
            if self.handle_map_event(drop=drop):
                confirm_timer.reset()
                result.add('event')
                continue
            if self.handle_retirement():
                confirm_timer.reset()
                continue
            if self.handle_walk_out_of_step():
                if walk_out_of_step:
                    raise MapWalkError('walk_out_of_step')
                else:
                    continue

            # Accident click
            if self.is_in_globe():
                self.os_globe_goto_map()
                confirm_timer.reset()
                continue
            if self.is_in_storage():
                self.storage_quit()
                confirm_timer.reset()
                continue
            if self.is_in_os_mission():
                self.os_mission_quit()
                confirm_timer.reset()
                continue
            if self.handle_os_game_tips():
                confirm_timer.reset()
                continue

            # Enemy searching
            if not enemy_searching_appear and self.enemy_searching_appear():
                enemy_searching_appear = True
                confirm_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
                    result.add('search')
                if self.is_in_map():
                    self.enemy_searching_color_initial()

            # 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()
                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()
                result.add('akashi')
                continue

            # Arrive
            # Check colors, because screen goes black when something is unlocking.
            if self.is_in_map() and IN_MAP.match_appear_on(self.device.image):
                self.update_os()
                current = self.view.backend.homo_loca
                logger.attr('homo_loca', current)
                if record is None or (current is not None and np.linalg.norm(
                        np.subtract(current, record)) < 3):
                    if confirm_timer.reached():
                        break
                else:
                    confirm_timer.reset()
                record = current
            else:
                confirm_timer.reset()

        result = '_'.join(result)
        logger.info(f'Walk stabled, result: {result}')
        self.device.screenshot_interval_set()
        return result
コード例 #19
0
    def _shipyard_buy_confirm(self, text, skip_first_screenshot=True):
        """
        Handles screen transitions to use/buy BPs

        Args:
            text (str): for handle_popup_confirm
            skip_first_screenshot (bool):
        """
        success = False
        append = self._shipyard_get_append()
        button = globals()[f'SHIPYARD_CONFIRM_{append}']
        ocr_timer = Timer(10, count=10).start()
        confirm_timer = Timer(1, count=2).start()
        self.interval_clear(button)

        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if ocr_timer.reached():
                logger.warning(
                    'Failed to detect for normal exit routine, resort to OCR check'
                )
                _, _, current = self._shipyard_get_total()
                if not current:
                    logger.info(
                        'Confirm action has completed, setting flag for exit')
                    self.interval_reset(button)
                    success = True
                ocr_timer.reset()
                continue

            if self.appear_then_click(button, offset=(20, 20), interval=3):
                continue

            if self.handle_popup_confirm(text):
                self.interval_reset(button)
                ocr_timer.reset()
                confirm_timer.reset()
                continue

            if self.story_skip():
                self.interval_reset(button)
                success = True
                ocr_timer.reset()
                confirm_timer.reset()
                continue

            if self.handle_info_bar():
                self.interval_reset(button)
                success = True
                ocr_timer.reset()
                confirm_timer.reset()
                continue

            # End
            if success and \
                self._shipyard_in_ui():
                if confirm_timer.reached():
                    break
            else:
                confirm_timer.reset()
コード例 #20
0
class OSFleet(OSCamera, Combat, Fleet, OSAsh):
    def _goto(self, location, expected=''):
        super()._goto(location, expected)
        self.predict_radar()
        self.map.show()

        if self.handle_ash_beacon_attack():
            # After ash attack, camera refocus to current fleet.
            self.camera = location
            self.update()

    def map_data_init(self, map_=None):
        """
        Create new map object, and use the shape of current zone
        """
        map_ = OSCampaignMap()
        map_.shape = self.zone.shape
        super().map_data_init(map_)

    def map_control_init(self):
        """
        Remove non-exist things like strategy, round.
        """
        # self.handle_strategy(index=1 if not self.fleets_reversed() else 2)
        self.update()
        # if self.handle_fleet_reverse():
        #     self.handle_strategy(index=1)
        self.hp_reset()
        self.hp_get()
        self.lv_reset()
        self.lv_get()
        self.ensure_edge_insight(preset=self.map.in_map_swipe_preset_data,
                                 swipe_limit=(6, 5))
        # self.full_scan(must_scan=self.map.camera_data_spawn_point)
        # self.find_current_fleet()
        # self.find_path_initial()
        # self.map.show_cost()
        # self.round_reset()
        # self.round_battle()

    def find_current_fleet(self):
        self.fleet_1 = self.camera

    @property
    def _walk_sight(self):
        sight = (-4, -1, 3, 2)
        return sight

    _os_map_event_handled = False

    def ambush_color_initial(self):
        self._os_map_event_handled = False

    def handle_ambush(self):
        """
        Treat map events as ambush, to trigger walk retrying
        """
        if self.handle_map_get_items():
            self._os_map_event_handled = True
            self.device.sleep(0.3)
            self.device.screenshot()
            return True
        elif self.handle_map_event():
            self.ensure_no_map_event()
            self._os_map_event_handled = True
            return True
        else:
            return False

    def handle_mystery(self, button=None):
        """
        After handle_ambush, if fleet has arrived, treat it as mystery, otherwise just ambush.
        """
        if self._os_map_event_handled and button.predict_fleet(
        ) and button.predict_current_fleet():
            return 'get_item'
        else:
            return False

    @staticmethod
    def _get_goto_expected(grid):
        """
        Argument `expected` used in _goto()
        """
        if grid.is_enemy:
            return 'combat'
        elif grid.is_resource or grid.is_meowfficer or grid.is_exclamation:
            return 'mystery'
        else:
            return ''

    def _hp_grid(self):
        hp_grid = super()._hp_grid()

        # Location of six HP bar, according to respective server for os
        if self.config.SERVER == 'en':
            hp_grid = ButtonGrid(origin=(35, 205),
                                 delta=(0, 100),
                                 button_shape=(66, 3),
                                 grid_shape=(1, 6))
        elif self.config.SERVER == 'jp':
            pass
        else:
            pass

        return hp_grid

    def hp_retreat_triggered(self):
        return False

    need_repair = [False, False, False, False, False, False]

    def hp_get(self):
        """
        Calculate current HP, also detects the wrench (Ship died, need to repair)
        """
        super().hp_get()
        ship_icon = self._hp_grid().crop((0, -67, 67, 0))
        need_repair = [
            TEMPLATE_EMPTY_HP.match(self.image_crop(button))
            for button in ship_icon.buttons
        ]
        self.need_repair = need_repair
        logger.attr('Repair icon', need_repair)

        if any(need_repair):
            for index, repair in enumerate(need_repair):
                if repair:
                    self._hp_has_ship[self.fleet_current_index][index] = True
                    self._hp[self.fleet_current_index][index] = 0

            logger.attr(
                'HP', ' '.join([
                    str(int(data * 100)).rjust(3) + '%' if use else '____'
                    for data, use in zip(self.hp, self.hp_has_ship)
                ]))

        return self.hp

    def lv_get(self, after_battle=False):
        pass

    def get_sea_grids(self):
        """
        Get sea grids on current view

        Returns:
            SelectedGrids:
        """
        sea = []
        for local in self.view:
            if not local.predict_sea() or local.predict_current_fleet():
                continue
            # local = np.array(location) - self.camera + self.view.center_loca
            location = np.array(
                local.location) + self.camera - self.view.center_loca
            location = tuple(location.tolist())
            if location == self.fleet_current or location not in self.map:
                continue
            sea.append(self.map[location])

        if len(self.fleet_current):
            center = self.fleet_current
        else:
            center = self.camera
        return SelectedGrids(sea).sort_by_camera_distance(center)

    def wait_until_camera_stable(self, skip_first_screenshot=True):
        """
        Wait until homo_loca stabled.
        DETECTION_BACKEND must be 'homography'.
        """
        logger.hr('Wait until camera stable')
        record = None
        confirm_timer = Timer(0.6, count=2).start()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            self.update_os()
            current = self.view.backend.homo_loca
            logger.attr('homo_loca', current)
            if record is None or (current is not None and np.linalg.norm(
                    np.subtract(current, record)) < 3):
                if confirm_timer.reached():
                    break
            else:
                confirm_timer.reset()

            record = current

        logger.info('Camera stabled')

    def wait_until_walk_stable(self,
                               confirm_timer=None,
                               skip_first_screenshot=False,
                               walk_out_of_step=True,
                               drop=None):
        """
        Wait until homo_loca stabled.
        DETECTION_BACKEND must be 'homography'.

        Args:
            confirm_timer (Timer):
            skip_first_screenshot (bool):
            walk_out_of_step (bool): If catch walk_out_of_step error.
                Default to True, use False in abyssal zones.
            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)
        if confirm_timer is None:
            confirm_timer = Timer(0.8, count=2)
        result = set()

        confirm_timer.reset()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            # Map event
            if self.handle_map_event(drop=drop):
                confirm_timer.reset()
                result.add('event')
                continue
            if self.handle_retirement():
                confirm_timer.reset()
                continue
            if self.handle_walk_out_of_step():
                if walk_out_of_step:
                    raise MapWalkError('walk_out_of_step')
                else:
                    continue

            # Accident click
            if self.is_in_globe():
                self.os_globe_goto_map()
                confirm_timer.reset()
                continue
            if self.is_in_storage():
                self.storage_quit()
                confirm_timer.reset()
                continue
            if self.is_in_os_mission():
                self.os_mission_quit()
                confirm_timer.reset()
                continue
            if self.handle_os_game_tips():
                confirm_timer.reset()
                continue

            # Enemy searching
            if not enemy_searching_appear and self.enemy_searching_appear():
                enemy_searching_appear = True
                confirm_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
                    result.add('search')
                if self.is_in_map():
                    self.enemy_searching_color_initial()

            # 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()
                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()
                result.add('akashi')
                continue

            # Arrive
            # Check colors, because screen goes black when something is unlocking.
            if self.is_in_map() and IN_MAP.match_appear_on(self.device.image):
                self.update_os()
                current = self.view.backend.homo_loca
                logger.attr('homo_loca', current)
                if record is None or (current is not None and np.linalg.norm(
                        np.subtract(current, record)) < 3):
                    if confirm_timer.reached():
                        break
                else:
                    confirm_timer.reset()
                record = current
            else:
                confirm_timer.reset()

        result = '_'.join(result)
        logger.info(f'Walk stabled, result: {result}')
        self.device.screenshot_interval_set()
        return result

    def port_goto(self):
        """
        A simple and poor implement to goto port. Searching port on radar.

        In OpSi, camera always focus to fleet when fleet is moving which mess up `self.goto()`.
        In most situation, we use auto search to clear a map in OpSi, and classic methods are deprecated.
        But we still need to move fleet toward port, this method is for this situation.

        Raises:
            MapWalkError: If unable to goto such grid.
                Probably clicking at land, center of port, or fleet itself.
        """
        confirm_timer = Timer(3, count=6).start()
        while 1:
            # Calculate destination
            grid = self.radar.port_predict(self.device.image)
            logger.info(f'Port route at {grid}')
            radar_arrive = np.linalg.norm(grid) == 0
            port_arrive = self.appear(PORT_ENTER, offset=(20, 20))
            if port_arrive:
                logger.info('Arrive port')
                break
            elif not port_arrive and radar_arrive:
                if confirm_timer.reached():
                    logger.warning(
                        'Arrive port on radar but port entrance not appear')
                    raise MapWalkError
                else:
                    logger.info(
                        'Arrive port on radar but port entrance not appear, confirming'
                    )
                    self.device.screenshot()
                    continue
            else:
                confirm_timer.reset()

            # Update local view
            self.update_os()
            self.predict()

            # Click way point
            grid = point_limit(grid, area=(-4, -2, 3, 2))
            grid = self.convert_radar_to_local(grid)
            self.device.click(grid)

            # Wait until arrived
            self.wait_until_walk_stable()

    def fleet_set(self, index=1, skip_first_screenshot=True):
        """
        Args:
            index (int): Target fleet_current_index
            skip_first_screenshot (bool):

        Returns:
            bool: If switched.
        """
        logger.hr(f'Fleet set to {index}')
        if self.fleet_selector.ensure_to_be(index):
            self.wait_until_camera_stable()
            return True
        else:
            return False

    def parse_fleet_filter(self):
        """
        Returns:
            list: List of BossFleet or str. Such as [Fleet-4, 'CallSubmarine', Fleet-2, Fleet-3, Fleet-1].
        """
        FLEET_FILTER.load(self.config.OpsiFleetFilter_Filter)
        fleets = FLEET_FILTER.apply([BossFleet(f) for f in [1, 2, 3, 4]])

        # Set standby location
        standby_list = [(-1, -1), (0, -1), (1, -1)]
        index = 0
        for fleet in fleets:
            if isinstance(fleet, BossFleet) and index < len(standby_list):
                fleet.standby_loca = standby_list[index]
                index += 1

        return fleets

    def question_goto(self, has_fleet_step=False):
        logger.hr('Question goto')
        while 1:
            # Update local view
            # Not screenshots taking, reuse the old one
            self.update_os()
            self.predict()
            self.predict_radar()

            # Calculate destination
            grids = self.radar.select(is_question=True)
            if grids:
                # Click way point
                grid = location_ensure(grids[0])
                grid = point_limit(grid, area=(-4, -2, 3, 2))
                if has_fleet_step:
                    grid = limit_walk(grid)
                grid = self.convert_radar_to_local(grid)
                self.device.click(grid)
            else:
                logger.info('No question mark to goto, stop')
                break

            # Wait until arrived
            # Having new screenshots
            self.wait_until_walk_stable(confirm_timer=Timer(1.5, count=4),
                                        walk_out_of_step=False)

    def boss_goto(self, location=(0, 0), has_fleet_step=False, drop=None):
        logger.hr('BOSS goto')
        while 1:
            # Update local view
            # Not screenshots taking, reuse the old one
            self.update_os()
            self.predict()
            self.predict_radar()

            # Calculate destination
            grids = self.radar.select(is_enemy=True)
            if grids:
                # Click way point
                grid = np.add(location_ensure(grids[0]), location)
                grid = point_limit(grid, area=(-4, -2, 3, 2))
                if has_fleet_step:
                    grid = limit_walk(grid)
                if grid == (0, 0):
                    logger.info(f'Arrive destination: boss {location}')
                    break
                grid = self.convert_radar_to_local(grid)
                self.device.click(grid)
            else:
                logger.info('No boss to goto, stop')
                break

            # Wait until arrived
            # Having new screenshots
            self.wait_until_walk_stable(confirm_timer=Timer(1.5, count=4),
                                        walk_out_of_step=False,
                                        drop=drop)

    def get_boss_leave_button(self):
        for grid in self.view:
            if grid.predict_current_fleet():
                return None

        grids = [grid for grid in self.view if grid.predict_caught_by_siren()]
        if len(grids) == 1:
            center = grids[0]
        elif len(grids) > 1:
            logger.warning(
                f'Found multiple fleets in boss ({grids}), use the center one')
            center = SelectedGrids(grids).sort_by_camera_distance(
                self.view.center_loca)[0]
        else:
            logger.warning('No fleet in boss, use camera center instead')
            center = self.view[self.view.center_loca]

        logger.info(f'Fleet in boss: {center}')
        # The left half grid next to the center grid.
        area = corner2inner(
            center.grid2screen(area2corner((1, 0.25, 1.5, 0.75))))
        button = Button(area=area, color=(), button=area, name='BOSS_LEAVE')
        return button

    def boss_leave(self, skip_first_screenshot=True):
        """
        Pages:
            in: is_in_map(), or combat_appear()
            out: is_in_map(), fleet not in boss.
        """
        logger.hr('BOSS leave')
        # Update local view
        self.update_os()

        click_timer = Timer(3)
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            # End
            if self.is_in_map():
                self.predict_radar()
                if self.radar.select(is_enemy=True):
                    logger.info('Fleet left boss, boss found')
                    break

            # Re-enter boss accidentally
            if self.combat_appear():
                self.ui_back(check_button=self.is_in_map)

            # Click leave button
            if self.is_in_map() and click_timer.reached():
                button = self.get_boss_leave_button()
                if button is not None:
                    self.device.click(button)
                    click_timer.reset()
                    continue
                else:
                    logger.info('Fleet left boss, current fleet found')
                    break

    def boss_clear(self, has_fleet_step=True):
        """
        All fleets take turns in attacking the boss.

        Args:
            has_fleet_step (bool):

        Returns:
            bool: If success to clear.

        Pages:
            in: Siren logger (abyssal), boss appeared.
            out: If success, dangerous or safe zone.
                If failed, still in abyssal.
        """
        logger.hr(f'BOSS clear', level=1)
        fleets = self.parse_fleet_filter()
        with self.stat.new(genre=inflection.underscore(
                self.config.task.command),
                           method=self.config.DropRecord_OpsiRecord) as drop:
            for fleet in fleets:
                logger.hr(f'Turn: {fleet}', level=2)
                if not isinstance(fleet, BossFleet):
                    self.os_order_execute(recon_scan=False,
                                          submarine_call=True)
                    continue

                # Attack
                self.fleet_set(fleet.fleet_index)
                self.handle_os_map_fleet_lock(enable=False)
                self.boss_goto(location=(0, 0),
                               has_fleet_step=has_fleet_step,
                               drop=drop)

                # End
                self.predict_radar()
                if self.radar.select(is_question=True):
                    logger.info('BOSS clear')
                    if drop.count:
                        drop.add(self.device.image)
                    self.map_exit()
                    return True

                # Standby
                self.boss_leave()
                if fleet.standby_loca != (0, 0):
                    self.boss_goto(location=fleet.standby_loca,
                                   has_fleet_step=has_fleet_step,
                                   drop=drop)
                else:
                    if drop.count:
                        drop.add(self.device.image)
                    break

        logger.critical('Unable to clear boss, fleets exhausted')
        return False

    def run_abyssal(self):
        """
        Handle double confirms and attack abyssal (siren logger) boss.

        Returns:
            bool: If success to clear.

        Pages:
            in: Siren logger (abyssal).
            out: If success, in a dangerous or safe zone.
                If failed, still in abyssal.
        """
        self.handle_os_map_fleet_lock(enable=False)

        def is_at_front(grid):
            # Grid location is usually to be (0, -2)
            x, y = grid.location
            return (abs(x) <= abs(y)) and (y < 0)

        while 1:
            self.device.screenshot()
            self.question_goto(has_fleet_step=True)

            if self.radar.select(is_enemy=True).filter(is_at_front):
                logger.info('Found boss at front')
                break
            else:
                logger.info('No boss at front, retry question_goto')
                continue

        result = self.boss_clear(has_fleet_step=True)
        return result

    def get_stronghold_percentage(self):
        """
        Get the clear status in siren stronghold.

        Returns:
            str: Usually in ['100', '80', '60', '40', '20', '0']
        """
        ocr = PercentageOcr(STRONGHOLD_PERCENTAGE,
                            letter=(255, 255, 255),
                            threshold=128,
                            name='STRONGHOLD_PERCENTAGE')
        result = ocr.ocr(self.device.image)
        result = result.rstrip('7Kk')
        for starter in ['100', '80', '60', '40', '20', '0']:
            if result.startswith(starter):
                result = starter
                logger.attr('STRONGHOLD_PERCENTAGE', result)
                return result

        logger.warning(f'Unexpected STRONGHOLD_PERCENTAGE: {result}')
        return result

    def get_second_fleet(self):
        """
        Get a second fleet to unlock fleet mechanism that requires 2 fleets.

        Returns:
            int:
        """
        current = self.fleet_selector.get()
        if current == 1:
            second = 2
        else:
            second = 1
        logger.attr('Second_fleet', second)
        return second

    @staticmethod
    def fleet_walk_limit(outside, step=3):
        if np.linalg.norm(outside) <= 3:
            return outside
        if step == 1:
            grids = np.array([
                (0, -1),
                (0, 1),
                (-1, 0),
                (1, 0),
            ])
        else:
            grids = np.array([
                (0, -3),
                (0, 3),
                (-3, 0),
                (3, 0),
                (2, -2),
                (2, 2),
                (-2, 2),
                (2, 2),
            ])
        degree = np.sum(grids * outside, axis=1) / np.linalg.norm(
            grids, axis=1) / np.linalg.norm(outside)
        return grids[np.argmax(degree)]

    _nearest_object_click_timer = Timer(2)

    def click_nearest_object(self):
        if not self._nearest_object_click_timer.reached():
            return False
        if not self.appear(MAP_GOTO_GLOBE, offset=(200, 20)):
            return False
        if self.appear(PORT_ENTER, offset=(20, 20)):
            return False

        self.update_os()
        self.view.predict()
        self.radar.predict(self.device.image)
        self.radar.show()
        nearest = self.radar.nearest_object()
        if nearest is None:
            self._nearest_object_click_timer.reset()
            return False

        step = 1 if self.appear(FLEET_EMP_DEBUFF, offset=(50, 20)) else 3
        nearest = self.fleet_walk_limit(nearest.location, step=step)
        try:
            nearest = self.convert_radar_to_local(nearest)
        except KeyError:
            logger.info('Radar grid not on local map')
            self._nearest_object_click_timer.reset()
            return False
        self.device.click(nearest)
        self._nearest_object_click_timer.reset()
コード例 #21
0
ファイル: fleet.py プロジェクト: gflong00/AzurLaneAutoScript
    def _goto(self, location, expected=''):
        """Goto a grid directly and handle ambush, air raid, mystery picked up, combat.

        Args:
            location (tuple, str, GridInfo): Destination.
        """
        location = location_ensure(location)
        result_mystery = ''

        while 1:
            sight = self.map.camera_sight
            self.in_sight(location, sight=(sight[0], 0, sight[2], sight[3]))
            self.focus_to_grid_center()
            grid = self.convert_map_to_grid(location)

            self.ambush_color_initial()
            self.enemy_searching_color_initial()
            grid.__str__ = location
            result = 'nothing'
            self.device.click(grid)
            arrived = False
            # Wait to confirm fleet arrived. It does't appear immediately if fleet in combat .
            arrive_timer = Timer(0.5 + self.round_wait, count=2)
            arrive_unexpected_timer = Timer(1.5 + self.round_wait, count=6)
            # Wait after ambushed.
            ambushed_retry = Timer(0.5)
            # If nothing happens, click again.
            walk_timeout = Timer(20)
            walk_timeout.start()

            while 1:
                self.device.screenshot()
                grid.image = np.array(self.device.image)

                # Ambush
                if self.handle_ambush():
                    ambushed_retry.start()
                    walk_timeout.reset()

                # Mystery
                mystery = self.handle_mystery(button=grid)
                if mystery:
                    self.mystery_count += 1
                    result = 'mystery'
                    result_mystery = mystery

                # Combat
                if self.config.ENABLE_MAP_FLEET_LOCK and not self.is_in_map():
                    if self.handle_retirement():
                        self.map_offensive()
                        walk_timeout.reset()
                    if self.handle_combat_low_emotion():
                        walk_timeout.reset()
                if self.combat_appear():
                    self.combat(
                        expected_end=self._expected_combat_end(expected),
                        fleet_index=self.fleet_current_index)
                    self.hp_get()
                    if self.hp_withdraw_triggered():
                        self.withdraw()
                    arrived = True if not self.config.MAP_HAS_MOVABLE_ENEMY else False
                    result = 'combat'
                    self.battle_count += 1
                    self.fleet_ammo -= 1
                    if 'siren' in expected or (
                            self.config.MAP_HAS_MOVABLE_ENEMY
                            and not expected):
                        self.siren_count += 1
                    elif self.map[location].may_enemy:
                        self.map[location].is_cleared = True

                    self.handle_boss_appear_refocus()
                    grid = self.convert_map_to_grid(location)
                    walk_timeout.reset()

                # Cat attack animation
                if self.handle_map_cat_attack():
                    walk_timeout.reset()
                    continue

                if self.handle_walk_out_of_step():
                    raise MapWalkError('walk_out_of_step')

                # Arrive
                if self.is_in_map() and \
                        (grid.predict_fleet() or
                         (walk_timeout.reached() and grid.predict_current_fleet())):
                    if not arrive_timer.started():
                        logger.info(f'Arrive {location2node(location)}')
                    arrive_timer.start()
                    arrive_unexpected_timer.start()
                    if not arrive_timer.reached():
                        continue
                    if expected and result not in expected:
                        if arrive_unexpected_timer.reached():
                            logger.warning('Arrive with unexpected result')
                        else:
                            continue
                    logger.info(
                        f'Arrive {location2node(location)} confirm. Result: {result}. Expected: {expected}'
                    )
                    arrived = True
                    break

                # End
                if ambushed_retry.started() and ambushed_retry.reached():
                    break
                if walk_timeout.reached():
                    logger.warning('Walk timeout. Retrying.')
                    self.ensure_edge_insight()
                    break

            # End
            if arrived:
                # Ammo grid needs to click again, otherwise the next click doesn't work.
                if self.map[location].may_ammo:
                    self.device.click(grid)
                break

        self.map[self.fleet_current].is_fleet = False
        self.map[location].wipe_out()
        self.map[location].is_fleet = True
        self.__setattr__('fleet_%s_location' % self.fleet_current_index,
                         location)
        if result_mystery == 'get_carrier':
            self.full_scan_carrier()
        if result == 'combat':
            self.round_battle()
        self.round_next()
        if self.round_is_new:
            self.full_scan_movable()
            self.find_path_initial()
            raise MapEnemyMoved
        self.find_path_initial()
コード例 #22
0
    def os_map_goto_globe(self, unpin=True, skip_first_screenshot=True):
        """
        Args:
            unpin (bool):
            skip_first_screenshot (bool):

        Pages:
            in: is_in_map
            out: is_in_globe
        """
        click_count = 0
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.appear_then_click(MAP_GOTO_GLOBE,
                                      offset=(200, 5),
                                      interval=5):
                click_count += 1
                if click_count >= 5:
                    # When there's zone exploration reward, AL just don't let you go.
                    logger.warning(
                        'Unable to goto globe, '
                        'there might be uncollected zone exploration rewards preventing exit'
                    )
                    raise GameTooManyClickError(
                        f'Too many click for a button: {MAP_GOTO_GLOBE}')
                continue
            if self.handle_map_event():
                continue
            # Popup: AUTO_SEARCH_REWARD appears slowly
            if self.appear_then_click(AUTO_SEARCH_REWARD,
                                      offset=(50, 50),
                                      interval=5):
                continue
            # Popup: Leaving current zone will terminate meowfficer searching.
            # Popup: Leaving current zone will retreat submarines
            # Searching reward will be shown after entering another zone.
            if self.handle_popup_confirm('GOTO_GLOBE'):
                continue

            # End
            if self.is_in_globe():
                break

        skip_first_screenshot = True
        confirm_timer = Timer(1, count=2).start()
        unpinned = 0
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if unpin:
                if self.handle_zone_pinned():
                    unpinned += 1
                    confirm_timer.reset()
                else:
                    if unpinned and confirm_timer.reached():
                        break
            else:
                if self.is_zone_pinned():
                    break
コード例 #23
0
    def os_mission_overview_accept(self):
        """
        Accept all missions in mission overview.

        Returns:
            bool: True if all missions accepted or no mission found.
                  False if unable to accept more missions.

        Pages:
            in: is_in_map
            out: is_in_map
        """
        logger.hr('OS mission overview accept', level=1)
        # is_in_map
        self.os_map_goto_globe(unpin=False)
        # is_in_globe
        self.ui_click(MISSION_OVERVIEW_ENTER,
                      check_button=MISSION_OVERVIEW_CHECK,
                      offset=(200, 20),
                      retry_wait=3,
                      skip_first_screenshot=True)

        # MISSION_OVERVIEW_CHECK
        confirm_timer = Timer(1, count=3).start()
        skip_first_screenshot = True
        success = True
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.info_bar_count():
                logger.info(
                    'Unable to accept missions, because reached the maximum number of missions'
                )
                success = False
                break
            if self.appear_then_click(MISSION_OVERVIEW_ACCEPT,
                                      offset=(20, 20),
                                      interval=0.2):
                confirm_timer.reset()
                continue
            else:
                # End
                if confirm_timer.reached():
                    success = True
                    break
            if self.appear_then_click(MISSION_OVERVIEW_ACCEPT_SINGLE,
                                      offset=(20, 20),
                                      interval=0.2):
                confirm_timer.reset()
                continue

        # is_in_globe
        self.ui_back(appear_button=MISSION_OVERVIEW_CHECK,
                     check_button=self.is_in_globe,
                     skip_first_screenshot=True)
        # is_in_map
        self.os_globe_goto_map()
        return success
コード例 #24
0
    def meow_get(self, skip_first_screenshot=True):
        """
        Transition through all the necessary screens
        to acquire each trained meowfficer
        Animation is waited for as the amount can vary
        Only gold variant meowfficer will prompt for
        confirmation

        Args:
            skip_first_screenshot (bool): Skip first
            screen shot or not

        Pages:
            in: MEOWFFICER_GET_CHECK
            out: MEOWFFICER_TRAIN
        """
        # Loop through possible screen transitions
        confirm_timer = Timer(1.5, count=3).start()
        count = 0
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.handle_meow_popup_dismiss():
                confirm_timer.reset()
                continue
            if self.appear(MEOWFFICER_GET_CHECK, offset=(40, 40), interval=3):
                count += 1
                logger.attr('Meow_get', count)
                with self.stat.new(genre="meowfficer_talent",
                                   method=self.config.
                                   DropRecord_MeowfficerTalent) as drop:
                    drop.add(self.device.image)
                    list_talent_btn, special_talent = self._get_meow_talent_grid(
                    )
                    if self.config.DropRecord_MeowfficerTalent != 'do_not':
                        self._meow_talent_cap_handle(list_talent_btn, drop)
                    if self.appear(MEOWFFICER_GOLD_CHECK, offset=(40, 40)):
                        if not self.config.MeowfficerTrain_RetainTalentedGold or not special_talent:
                            self._meow_skip_lock()
                            skip_first_screenshot = True
                            confirm_timer.reset()
                            continue
                        self._meow_apply_lock()

                    if self.appear(MEOWFFICER_PURPLE_CHECK, offset=(40, 40)):
                        if self.config.MeowfficerTrain_RetainTalentedPurple and special_talent:
                            self._meow_apply_lock()

                    # Susceptible to exception when collecting multiple
                    # Mitigate by popping click_record
                    self.device.click(MEOWFFICER_TRAIN_CLICK_SAFE_AREA)
                    self.device.click_record.pop()
                    confirm_timer.reset()
                    self.interval_reset(MEOWFFICER_GET_CHECK)
                    continue

            # End
            if self.appear(MEOWFFICER_TRAIN_START, offset=(20, 20)):
                if confirm_timer.reached():
                    break
            else:
                confirm_timer.reset()
コード例 #25
0
class EnemySearchingHandler(InfoHandler):
    MAP_ENEMY_SEARCHING_OVERLAY_TRANSPARENCY_THRESHOLD = 0.5  # Usually (0.70, 0.80).
    MAP_ENEMY_SEARCHING_TIMEOUT_SECOND = 5
    in_stage_timer = Timer(0.5, count=2)
    stage_entrance = None

    map_is_clear = False  # Will be override in fast_forward.py

    def enemy_searching_color_initial(self):
        MAP_ENEMY_SEARCHING.load_color(self.device.image)

    def enemy_searching_appear(self):
        return red_overlay_transparency(
            MAP_ENEMY_SEARCHING.color, get_color(self.device.image, MAP_ENEMY_SEARCHING.area)
        ) > self.MAP_ENEMY_SEARCHING_OVERLAY_TRANSPARENCY_THRESHOLD

    def handle_enemy_flashing(self):
        self.device.sleep(1.2)

    def handle_in_stage(self):
        if self.is_in_stage():
            if self.in_stage_timer.reached():
                logger.info('In stage.')
                self.device.send_notification('AzurLaneAutoScript', 'Map cleared')
                self.ensure_no_info_bar(timeout=1.2)
                raise CampaignEnd('In stage.')
            else:
                return False
        else:
            if self.appear(MAP_PREPARATION) or self.appear(FLEET_PREPARATION):
                self.device.click(MAP_PREPARATION_CANCEL)
            self.in_stage_timer.reset()
            return False

    def is_in_stage(self):
        appear = [self.appear(check, offset=(20, 20)) for check in [CAMPAIGN_CHECK, EVENT_CHECK, SP_CHECK]]
        if not any(appear):
            return False

        # campaign_extract_name_image in CampaignOcr.
        try:
            if hasattr(self, 'campaign_extract_name_image') \
                    and not len(self.campaign_extract_name_image(self.device.image)):
                return False
        except IndexError:
            return False

        return True

    def is_in_map(self):
        return self.appear(IN_MAP)

    def is_event_animation(self):
        """
        Animation in events after cleared an enemy.

        Returns:
            bool: If animation appearing.
        """
        return False

    def handle_in_map_with_enemy_searching(self):
        if not self.is_in_map():
            return False

        timeout = Timer(self.MAP_ENEMY_SEARCHING_TIMEOUT_SECOND)
        appeared = False
        while 1:
            self.device.screenshot()
            if self.is_event_animation():
                continue
            if self.is_in_map():
                timeout.start()
            else:
                timeout.reset()

            if self.handle_in_stage():
                return True
            if self.handle_story_skip():
                self.ensure_no_story()
                timeout.limit = 10
                timeout.reset()

            if self.handle_guild_popup_cancel():
                self.config.GUILD_POPUP_TRIGGERED = True
                timeout.limit = 10
                timeout.reset()
                continue

            # End
            if self.enemy_searching_appear():
                appeared = True
            else:
                if appeared:
                    self.handle_enemy_flashing()
                    self.device.sleep(1)
                    logger.info('Enemy searching appeared.')
                    break
                self.enemy_searching_color_initial()
            if timeout.reached():
                logger.info('Enemy searching timeout.')
                break

        self.device.screenshot()
        return True

    def handle_in_map_no_enemy_searching(self):
        if not self.is_in_map():
            return False

        self.device.sleep((1, 1.2))
        return True
コード例 #26
0
ファイル: device.py プロジェクト: nEEtdo0d/AzurLaneAutoScript
class Device(Screenshot, Control, AppControl):
    _screen_size_checked = False
    detect_record = set()
    click_record = deque(maxlen=15)
    stuck_timer = Timer(60, count=60).start()
    stuck_timer_long = Timer(180, count=180).start()
    stuck_long_wait_list = ['BATTLE_STATUS_S', 'PAUSE', 'LOGIN_CHECK']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.screenshot_interval_set()

    def handle_night_commission(self, daily_trigger='21:00', threshold=30):
        """
        Args:
            daily_trigger (int): Time for commission refresh.
            threshold (int): Seconds around refresh time.

        Returns:
            bool: If handled.
        """
        update = get_server_next_update(daily_trigger=daily_trigger)
        now = datetime.now()
        diff = (update.timestamp() - now.timestamp()) % 86400
        if threshold < diff < 86400 - threshold:
            return False

        if GET_MISSION.match(self.image, offset=True):
            logger.info('Night commission appear.')
            self.click(GET_MISSION)
            return True

        return False

    def screenshot(self):
        """
        Returns:
            np.ndarray:
        """
        self.stuck_record_check()
        super().screenshot()
        if self.handle_night_commission():
            super().screenshot()

        return self.image

    def stuck_record_add(self, button):
        self.detect_record.add(str(button))

    def stuck_record_clear(self):
        self.detect_record = set()
        self.stuck_timer.reset()
        self.stuck_timer_long.reset()

    def stuck_record_check(self):
        """
        Raises:
            GameStuckError:
        """
        reached = self.stuck_timer.reached()
        reached_long = self.stuck_timer_long.reached()

        if not reached:
            return False
        if not reached_long:
            for button in self.stuck_long_wait_list:
                if button in self.detect_record:
                    return False

        logger.warning('Wait too long')
        logger.warning(f'Waiting for {self.detect_record}')
        self.stuck_record_clear()

        raise GameStuckError(f'Wait too long')

    def handle_control_check(self, button):
        self.stuck_record_clear()
        self.click_record_add(button)
        self.click_record_check()

    def click_record_add(self, button):
        self.click_record.append(str(button))

    def click_record_clear(self):
        self.click_record.clear()

    def click_record_check(self):
        """
        Raises:
            GameTooManyClickError:
        """
        count = {}
        for key in self.click_record:
            count[key] = count.get(key, 0) + 1
        count = sorted(count.items(), key=lambda item: item[1])
        if count[0][1] >= 12:
            logger.warning(f'Too many click for a button: {count[0][0]}')
            logger.warning(
                f'History click: {[str(prev) for prev in self.click_record]}')
            self.click_record_clear()
            raise GameTooManyClickError(
                f'Too many click for a button: {count[0][0]}')
        if len(count) >= 2 and count[0][1] >= 6 and count[1][1] >= 6:
            logger.warning(
                f'Too many click between 2 buttons: {count[0][0]}, {count[1][0]}'
            )
            logger.warning(
                f'History click: {[str(prev) for prev in self.click_record]}')
            self.click_record_clear()
            raise GameTooManyClickError(
                f'Too many click between 2 buttons: {count[0][0]}, {count[1][0]}'
            )

    def disable_stuck_detection(self):
        """
        Disable stuck detection and its handler. Usually uses in semi auto and debugging.
        """
        logger.info('Disable stuck detection')

        def empty_function(*arg, **kwargs):
            return False

        self.click_record_check = empty_function
        self.stuck_record_check = empty_function

    def app_start(self):
        if not self.config.Error_HandleError:
            logger.critical('No app stop/start, because HandleError disabled')
            logger.critical(
                'Please enable Alas.Error.HandleError or manually login to AzurLane'
            )
            raise RequestHumanTakeover
        super().app_start()
        self.stuck_record_clear()
        self.click_record_clear()

    def app_stop(self):
        if not self.config.Error_HandleError:
            logger.critical('No app stop/start, because HandleError disabled')
            logger.critical(
                'Please enable Alas.Error.HandleError or manually login to AzurLane'
            )
            raise RequestHumanTakeover
        super().app_stop()
        self.stuck_record_clear()
        self.click_record_clear()
コード例 #27
0
    def _goto(self, location, expected=''):
        """Goto a grid directly and handle ambush, air raid, mystery picked up, combat.

        Args:
            location (tuple, str, GridInfo): Destination.
            expected (str): Expected result on destination grid, such as 'combat', 'combat_siren', 'mystery'.
                Will give a waring if arrive with unexpected result.
        """
        location = location_ensure(location)
        result_mystery = ''
        self.movable_before = self.map.select(is_siren=True)
        self.movable_before_normal = self.map.select(is_enemy=True)
        if self.hp_retreat_triggered():
            self.withdraw()
        is_portal = self.map[location].is_portal
        # The upper grid is submarine, may mess up predict_fleet()
        may_submarine_icon = self.map.grid_covered(self.map[location], location=[(0, -1)])
        may_submarine_icon = may_submarine_icon and self.fleet_submarine_location == may_submarine_icon[0].location

        while 1:
            self.in_sight(location, sight=self._walk_sight)
            self.focus_to_grid_center()
            grid = self.convert_global_to_local(location)

            self.ambush_color_initial()
            self.enemy_searching_color_initial()
            grid.__str__ = location
            result = 'nothing'

            self.device.click(grid)
            arrived = False
            # Wait to confirm fleet arrived. It does't appear immediately if fleet in combat.
            extra = 0
            if self.config.Submarine_Mode == 'hunt_only':
                extra += 4.5
            if self.config.MAP_HAS_LAND_BASED and grid.is_mechanism_trigger:
                extra += grid.mechanism_wait
            arrive_timer = Timer(0.5 + self.round_wait + extra, count=2)
            arrive_unexpected_timer = Timer(1.5 + self.round_wait + extra, count=6)
            # Wait after ambushed.
            ambushed_retry = Timer(0.5)
            # If nothing happens, click again.
            walk_timeout = Timer(20)
            walk_timeout.start()

            while 1:
                self.device.screenshot()
                self.view.update(image=self.device.image)
                if is_portal:
                    self.update()
                    grid = self.view[self.view.center_loca]

                # Combat
                if self.config.Campaign_UseFleetLock and not self.is_in_map():
                    if self.handle_retirement():
                        self.map_offensive()
                        walk_timeout.reset()
                    if self.handle_combat_low_emotion():
                        walk_timeout.reset()
                if self.combat_appear():
                    self.combat(
                        expected_end=self._expected_end(expected),
                        fleet_index=self.fleet_show_index,
                        submarine_mode=self._submarine_mode(expected)
                    )
                    self.hp_get()
                    self.lv_get(after_battle=True)
                    arrived = True if not self.config.MAP_HAS_MOVABLE_ENEMY else False
                    result = 'combat'
                    self.battle_count += 1
                    self.fleet_ammo -= 1
                    if 'siren' in expected or (self.config.MAP_HAS_MOVABLE_ENEMY and not expected):
                        self.siren_count += 1
                    elif self.map[location].may_enemy:
                        self.map[location].is_cleared = True

                    if self.catch_camera_repositioning(self.map[location]):
                        self.handle_boss_appear_refocus()
                    if self.config.MAP_FOCUS_ENEMY_AFTER_BATTLE:
                        self.camera = location
                        self.update()
                    grid = self.convert_global_to_local(location)
                    arrive_timer = Timer(0.5 + extra, count=2)
                    arrive_unexpected_timer = Timer(1.5 + extra, count=6)
                    walk_timeout.reset()

                # Ambush
                if self.handle_ambush():
                    self.hp_get()
                    self.lv_get(after_battle=True)
                    walk_timeout.reset()
                    self.view.update(image=self.device.image)
                    if not (grid.predict_fleet() and grid.predict_current_fleet()):
                        ambushed_retry.start()

                # Mystery
                mystery = self.handle_mystery(button=grid)
                if mystery:
                    self.mystery_count += 1
                    result = 'mystery'
                    result_mystery = mystery

                # Cat attack animation
                if self.handle_map_cat_attack():
                    walk_timeout.reset()
                    continue

                # Guild popup
                # Usually handled in combat_status, but sometimes delayed until after battle on slow PCs.
                if self.handle_guild_popup_cancel():
                    walk_timeout.reset()
                    continue

                if self.handle_walk_out_of_step():
                    raise MapWalkError('walk_out_of_step')

                # Arrive
                arrive_predict = ''
                arrive_checker = False
                if self.is_in_map():
                    if not may_submarine_icon and grid.predict_fleet():
                        arrive_predict = '(is_fleet)'
                        arrive_checker = True
                    elif may_submarine_icon and grid.predict_current_fleet():
                        arrive_predict = '(may_submarine_icon, is_current_fleet)'
                        arrive_checker = True
                    elif self.config.MAP_WALK_USE_CURRENT_FLEET and grid.predict_current_fleet():
                        arrive_predict = '(MAP_WALK_USE_CURRENT_FLEET, is_current_fleet)'
                        arrive_checker = True
                    elif walk_timeout.reached() and grid.predict_current_fleet():
                        arrive_predict = '(walk_timeout, is_current_fleet)'
                        arrive_checker = True
                if arrive_checker:
                    if not arrive_timer.started():
                        logger.info(f'Arrive {location2node(location)} {arrive_predict}'.strip())
                    arrive_timer.start()
                    arrive_unexpected_timer.start()
                    if not arrive_timer.reached():
                        continue
                    if expected and result not in expected:
                        if arrive_unexpected_timer.reached():
                            logger.warning('Arrive with unexpected result')
                        else:
                            continue
                    if is_portal:
                        location = self.map[location].portal_link
                        self.camera = location
                    logger.info(f'Arrive {location2node(location)} confirm. Result: {result}. Expected: {expected}')
                    arrived = True
                    break

                # Story
                if expected == 'story':
                    if self.handle_story_skip():
                        result = 'story'
                        continue

                # End
                if ambushed_retry.started() and ambushed_retry.reached():
                    break
                if walk_timeout.reached():
                    logger.warning('Walk timeout. Retrying.')
                    self.predict()
                    self.ensure_edge_insight(skip_first_update=False)
                    break

            # End
            if arrived:
                # Ammo grid needs to click again, otherwise the next click doesn't work.
                if self.map[location].may_ammo:
                    self.device.click(grid)
                break

        self.map[self.fleet_current].is_fleet = False
        self.map[location].wipe_out()
        self.map[location].is_fleet = True
        self.__setattr__('fleet_%s_location' % self.fleet_current_index, location)
        if result_mystery == 'get_carrier':
            self.full_scan_carrier()
        if result == 'combat':
            self.round_battle(after_battle=True)
            self.predict()
        self.round_next()
        if self.round_is_new:
            if result != 'combat':
                self.predict()
            self.full_scan_movable(enemy_cleared=result == 'combat')
            self.find_path_initial()
            raise MapEnemyMoved
        if self.round_maze_changed:
            self.find_path_initial()
            raise MapEnemyMoved
        self.find_path_initial()
コード例 #28
0
ファイル: reward.py プロジェクト: yang1987/AzurLaneAutoScript
    def _reward_mission(self):
        """
        Returns:
            bool: If rewarded.
        """
        if not self.config.ENABLE_MISSION_REWARD:
            return False

        logger.hr('Mission reward')
        if not self.appear(MISSION_NOTICE):
            logger.info('No mission reward')
            return False

        self.ui_goto(page_mission, skip_first_screenshot=True)

        reward = False
        exit_timer = Timer(2)
        click_timer = Timer(1)
        timeout = Timer(10)
        exit_timer.start()
        timeout.start()
        while 1:
            self.device.screenshot()

            for button in [GET_ITEMS_1, GET_ITEMS_2]:
                if self.appear_then_click(button, offset=(30, 30), interval=1):
                    exit_timer.reset()
                    timeout.reset()
                    reward = True
                    continue

            for button in [MISSION_MULTI, MISSION_SINGLE]:
                if not click_timer.reached():
                    continue
                if self.appear_then_click(button, interval=1):
                    exit_timer.reset()
                    click_timer.reset()
                    timeout.reset()
                    continue

            if not self.appear(MISSION_CHECK):
                if self.appear_then_click(GET_SHIP, interval=1):
                    click_timer.reset()
                    exit_timer.reset()
                    timeout.reset()
                    continue

            if self.story_skip():
                click_timer.reset()
                exit_timer.reset()
                timeout.reset()
                continue

            # End
            if reward and exit_timer.reached():
                break
            if timeout.reached():
                logger.warning('Wait get items timeout.')
                break

        self.ui_goto(page_main, skip_first_screenshot=True)
        return reward
コード例 #29
0
class MapOperation(MysteryHandler, FleetPreparation, Retirement,
                   FastForwardHandler):
    map_cat_attack_timer = Timer(2)

    def fleet_switch_click(self):
        """
        Switch fleet.
        """
        logger.info('Switch over')
        if self.appear_then_click(SWITCH_OVER):
            pass
        else:
            logger.warning('No buttons detected.')

        self.device.sleep((1, 1.5))
        # self.ensure_no_info_bar()

    def enter_map(self, button, mode='normal'):
        """Enter a campaign.

        Args:
            button: Campaign to enter.
            mode (str): 'normal' or 'hard' or 'cd'
        """
        logger.hr('Enter map')
        campaign_timer = Timer(2)
        map_timer = Timer(1)
        fleet_timer = Timer(1)
        checked_in_map = False
        while 1:
            self.device.screenshot()

            if not checked_in_map and self.is_in_map():
                logger.info('Already in map, skip enter_map.')
                return False
            else:
                checked_in_map = True

            # Map preparation
            if map_timer.reached() and self.appear(MAP_PREPARATION):
                self.device.sleep(0.3)  # Wait for map information.
                self.device.screenshot()
                if self.handle_map_clear_mode_stop():
                    self.enter_map_cancel()
                    raise ScriptEnd(
                        f'Reach condition: {self.config.CLEAR_MODE_STOP_CONDITION}'
                    )
                self.handle_fast_forward()
                self.device.click(MAP_PREPARATION)
                map_timer.reset()
                campaign_timer.reset()
                continue

            # Fleet preparation
            if fleet_timer.reached() and self.appear(FLEET_PREPARATION):
                if self.config.ENABLE_FLEET_CONTROL:
                    if mode == 'normal' or mode == 'hard':
                        self.fleet_preparation()
                self.device.click(FLEET_PREPARATION)
                fleet_timer.reset()
                campaign_timer.reset()
                continue

            # Retire
            if self.handle_retirement():
                continue

            # Emotion
            if self.handle_combat_low_emotion():
                continue

            # Urgent commission
            if self.handle_urgent_commission():
                continue

            # Story skip
            if self.handle_story_skip():
                campaign_timer.reset()
                continue

            # Enter campaign
            if campaign_timer.reached() and self.is_in_stage():
                self.device.click(button)
                campaign_timer.reset()
                continue

            # End
            if self.handle_in_map_with_enemy_searching():
                break

        return True

    def enter_map_cancel(self, skip_first_screenshot=True):
        logger.hr('Enter map cancel')
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.appear(MAP_PREPARATION) or self.appear(FLEET_PREPARATION):
                self.device.click(MAP_PREPARATION_CANCEL)
                continue

            if self.is_in_stage():
                break

        return True

    def withdraw(self):
        """
        Withdraw campaign.
        """
        logger.hr('Map withdraw')
        while 1:
            self.device.screenshot()

            if self.handle_popup_confirm():
                continue
            if self.appear_then_click(WITHDRAW, interval=2):
                continue

            # End
            if self.handle_in_stage():
                raise CampaignEnd('Withdraw')

    def handle_map_cat_attack(self):
        """
        Click to skip the animation when cat attacks.
        """
        if not self.map_cat_attack_timer.reached():
            return False
        if np.sum(
                color_similarity_2d(self.image_area(MAP_CAT_ATTACK),
                                    (255, 231, 123)) > 221) > 100:
            logger.info('Skip map cat attack')
            self.device.click(MAP_CAT_ATTACK)
            self.map_cat_attack_timer.reset()
            return True

        return False

    def handle_fleet_reverse(self):
        """
        The game chooses the fleet with a smaller index to be the first fleet,
        no matter what we choose in fleet preparation.

        Returns:
            bool: Fleet changed
        """
        if (self.config.FLEET_2
                == 0) or (self.config.FLEET_2 > self.config.FLEET_1):
            return False

        self.fleet_switch_click()
        return True

    def handle_spare_fleet(self):
        pass
コード例 #30
0
class InfoHandler(ModuleBase):
    """
    Class to handle all kinds of message.
    """
    """
    Info bar
    """

    def info_bar_count(self):
        if self.appear(INFO_BAR_3):
            return 3
        elif self.appear(INFO_BAR_2):
            return 2
        elif self.appear(INFO_BAR_1):
            return 1
        else:
            return 0

    def handle_info_bar(self):
        if self.info_bar_count():
            self.wait_until_disappear(INFO_BAR_1)
            return True
        else:
            return False

    def ensure_no_info_bar(self, timeout=0.6):
        timeout = Timer(timeout)
        timeout.start()
        while 1:
            self.device.screenshot()
            self.handle_info_bar()

            if timeout.reached():
                break

    """
    Popup info
    """
    _popup_offset = (3, 30)

    def handle_popup_confirm(self, name=''):
        if self.appear(POPUP_CANCEL, offset=self._popup_offset) \
                and self.appear(POPUP_CONFIRM, offset=self._popup_offset, interval=2):
            POPUP_CONFIRM.name = POPUP_CONFIRM.name + '_' + name
            self.device.click(POPUP_CONFIRM)
            POPUP_CONFIRM.name = POPUP_CONFIRM.name[:-len(name) - 1]
            return True
        else:
            return False

    def handle_popup_cancel(self, name=''):
        if self.appear(POPUP_CONFIRM, offset=self._popup_offset) \
                and self.appear(POPUP_CANCEL, offset=self._popup_offset, interval=2):
            POPUP_CANCEL.name = POPUP_CANCEL.name + '_' + name
            self.device.click(POPUP_CANCEL)
            POPUP_CANCEL.name = POPUP_CANCEL.name[:-len(name) - 1]
            return True
        else:
            return False

    def handle_popup_single(self, name=''):
        if self.appear(GET_MISSION, offset=self._popup_offset, interval=2):
            prev_name = GET_MISSION.name
            GET_MISSION.name = POPUP_CONFIRM.name + '_' + name
            self.device.click(GET_MISSION)
            GET_MISSION.name = prev_name
            return True

        return False

    def handle_urgent_commission(self, save_get_items=None):
        """
        Args:
            save_get_items (bool):

        Returns:
            bool:
        """
        if save_get_items is None:
            save_get_items = self.config.ENABLE_SAVE_GET_ITEMS

        appear = self.appear(GET_MISSION, offset=True, interval=2)
        if appear:
            logger.info('Get urgent commission')
            if save_get_items:
                self.handle_info_bar()
                self.device.save_screenshot('get_mission')
            self.device.click(GET_MISSION)
        return appear

    def handle_combat_low_emotion(self):
        if not self.config.IGNORE_LOW_EMOTION_WARN:
            return False

        return self.handle_popup_confirm('IGNORE_LOW_EMOTION')

    def handle_use_data_key(self):
        if not self.config.USE_DATA_KEY:
            return False

        if not self.appear(POPUP_CONFIRM, offset=self._popup_offset) \
                and not self.appear(POPUP_CANCEL, offset=self._popup_offset, interval=2):
            return False

        if self.appear(USE_DATA_KEY, offset=(20, 20)):
            self.device.click(USE_DATA_KEY_NOTIFIED)
            self.device.sleep((0.5, 0.8))
            return self.handle_popup_confirm('USE_DATA_KEY')

        return False

    """
    Guild popup info
    """
    def handle_guild_popup_confirm(self):
        if self.appear(GUILD_POPUP_CANCEL, offset=self._popup_offset) \
                and self.appear(GUILD_POPUP_CONFIRM, offset=self._popup_offset, interval=2):
            self.device.click(GUILD_POPUP_CONFIRM)
            return True

        return False

    def handle_guild_popup_cancel(self):
        if self.appear(GUILD_POPUP_CONFIRM, offset=self._popup_offset) \
                and self.appear(GUILD_POPUP_CANCEL, offset=self._popup_offset, interval=2):
            self.device.click(GUILD_POPUP_CANCEL)
            return True

        return False

    """
    Mission popup info
    """
    def handle_mission_popup_go(self):
        if self.appear(MISSION_POPUP_ACK, offset=self._popup_offset) \
                and self.appear(MISSION_POPUP_GO, offset=self._popup_offset, interval=2):
            self.device.click(MISSION_POPUP_GO)
            return True

        return False

    def handle_mission_popup_ack(self):
        if self.appear(MISSION_POPUP_GO, offset=self._popup_offset) \
                and self.appear(MISSION_POPUP_ACK, offset=self._popup_offset, interval=2):
            self.device.click(MISSION_POPUP_ACK)
            return True

        return False

    """
    Story
    """
    story_popup_timout = Timer(10, count=20)
    map_has_fast_forward = False  # Will be override in fast_forward.py

    # Area to detect the options, should include at least 3 options.
    _story_option_area = (730, 188, 1140, 480)
    # Background color of the left part of the option.
    _story_option_color = (99, 121, 156)
    _story_option_timer = Timer(2)

    def _story_option_buttons(self):
        """
        Returns:
            list[Button]: List of story options, from upper to bottom. If no option found, return an empty list.
        """
        image = color_similarity_2d(self.image_area(self._story_option_area), color=self._story_option_color) > 225
        x_count = np.where(np.sum(image, axis=0) > 40)[0]
        if not len(x_count):
            return []
        x_min, x_max = np.min(x_count), np.max(x_count)

        parameters = {
            # Option is 300`320px x 50~52px.
            'height': 280,
            'width': 45,
            'distance': 50,
            # Chooses the relative height at which the peak width is measured as a percentage of its prominence.
            # 1.0 calculates the width of the peak at its lowest contour line,
            # while 0.5 evaluates at half the prominence height.
            # Must be at least 0.
            'rel_height': 5,
        }
        y_count = np.sum(image, axis=1)
        peaks, properties = signal.find_peaks(y_count, **parameters)
        buttons = []
        total = len(peaks)
        if not total:
            return []
        for n, bases in enumerate(zip(properties['left_bases'], properties['right_bases'])):
            area = (x_min, bases[0], x_max, bases[1])
            area = area_pad(area_offset(area, offset=self._story_option_area[:2]), pad=5)
            buttons.append(
                Button(area=area, color=self._story_option_color, button=area, name=f'STORY_OPTION_{n + 1}_OF_{total}'))

        return buttons

    def story_skip(self):
        if self.story_popup_timout.started() and not self.story_popup_timout.reached():
            if self.handle_popup_confirm('STORY_SKIP'):
                self.story_popup_timout = Timer(10)
                self.interval_reset(STORY_SKIP)
                self.interval_reset(STORY_LETTERS_ONLY)
                return True
        if self.appear(STORY_LETTER_BLACK) and self.appear_then_click(STORY_LETTERS_ONLY, offset=True, interval=2):
            self.story_popup_timout.reset()
            return True
        if self._story_option_timer.reached() and self.appear(STORY_SKIP, offset=True, interval=0):
            options = self._story_option_buttons()
            if len(options):
                self.device.click(options[0])
                self._story_option_timer.reset()
                self.story_popup_timout.reset()
                self.interval_reset(STORY_SKIP)
                self.interval_reset(STORY_LETTERS_ONLY)
                return True
        if self.appear_then_click(STORY_SKIP, offset=True, interval=2):
            self.story_popup_timout.reset()
            return True
        if self.appear_then_click(GAME_TIPS, offset=(20, 20), interval=2):
            self.story_popup_timout.reset()
            return True

        return False

    def handle_story_skip(self):
        if self.map_has_fast_forward:
            return False

        return self.story_skip()

    def ensure_no_story(self, skip_first_screenshot=True):
        logger.info('Ensure no story')
        story_timer = Timer(3, count=6).start()
        while 1:
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            if self.story_skip():
                story_timer.reset()

            if story_timer.reached():
                break

    def handle_map_after_combat_story(self):
        if not self.config.MAP_HAS_MAP_STORY:
            return False

        self.ensure_no_story()