예제 #1
0
    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)

        book = books.choose(tier_max=self.config.TACTICAL_BOOK_TIER_MAX,
                            tier_min=self.config.TACTICAL_BOOK_TIER_MIN,
                            exp=self.config.TACTICAL_EXP_FIRST)

        self.device.click(book.button)
        self.device.sleep((0.3, 0.5))
예제 #2
0
    def name_to_zone(self, name):
        """
        Args:
            name (str, int, Zone): Name in CN/EN/JP, zone id, or Zone instance.

        Returns:
            Zone:
        """
        if isinstance(name, Zone):
            return name
        elif isinstance(name, int):
            return self.zones.select(zone_id=name)[0]
        elif isinstance(name, str) and name.isdigit():
            return self.zones.select(zone_id=int(name))[0]
        else:

            def parse_name(n):
                n = str(n).replace(' ', '').lower()
                return n

            name = parse_name(name)
            for zone in self.zones:
                if name == parse_name(zone.cn) or name == parse_name(
                        zone.en) or name == parse_name(zone.jp):
                    return zone
            logger.warning(f'Unable to find OS globe zone: {name}')
            raise ScriptError(f'Unable to find OS globe zone: {name}')
예제 #3
0
    def name_to_zone(self, name):
        """
        Convert a name from various format to zone instance.

        Args:
            name (str, int, Zone): Name in CN/EN/JP/TW, zone id, or Zone instance.

        Returns:
            Zone:

        Raises:
            ScriptError: If Unable to find such zone.
        """
        if isinstance(name, Zone):
            return name
        elif isinstance(name, int):
            return self.zones.select(zone_id=name)[0]
        elif isinstance(name, str) and name.isdigit():
            return self.zones.select(zone_id=int(name))[0]
        else:
            def parse_name(n):
                n = str(n).replace(' ', '').lower()
                return n

            name = parse_name(name)
            for zone in self.zones:
                if name == parse_name(zone.cn):
                    return zone
                if name == parse_name(zone.en):
                    return zone
                if name == parse_name(zone.jp):
                    return zone
                if name == parse_name(zone.tw):
                    return zone
            raise ScriptError(f'Unable to find OS globe zone: {name}')
예제 #4
0
    def predict_enemy_genre(self):
        image_dic = {}
        scaling_dic = self.config.MAP_ENEMY_GENRE_DETECTION_SCALING
        for name, template in self.template_enemy_genre.items():
            if template is None:
                logger.warning(f'Enemy detection template not found: {name}')
                logger.warning(
                    'Please create it with dev_tools/relative_record.py or dev_tools/relative_crop.py, '
                    'then place it under ./assets/<server>/template')
                raise ScriptError(
                    f'Enemy detection template not found: {name}')

            short_name = name[6:] if name.startswith('Siren_') else name
            scaling = scaling_dic.get(short_name, 1)
            scaling = (
                scaling, ) if not isinstance(scaling, tuple) else scaling
            for scale in scaling:
                if scale not in image_dic:
                    shape = tuple(
                        np.round(np.array((60, 60)) * scale).astype(int))
                    image_dic[scale] = rgb2gray(
                        self.relative_crop((-0.5, -1, 0.5, 0), shape=shape))

                if template.match(
                        image_dic[scale],
                        similarity=self.config.MAP_ENEMY_GENRE_SIMILARITY):
                    return name

        return None
예제 #5
0
    def _tactical_books_get(self):
        """
        Get books. Handle loadings, wait 10 times at max.
        When TACTICAL_CLASS_START appears, game may stuck in loading, wait and retry detection.
        If loading still exists, raise ScriptError.

        Returns:
            BookGroup:

        Pages:
            in: TACTICAL_CLASS_START
            out: TACTICAL_CLASS_START
        """
        for n in range(10):
            self.device.screenshot()
            self.handle_info_bar()  # info_bar appears when get ship in Launch Ceremony commissions
            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)}')

            # End
            if books:
                return books
            else:
                self.device.sleep(3)
                continue

        logger.warning('No book found.')
        raise ScriptError('No book found, after 10 attempts.')
    def execute_a_battle(self):
        logger.hr(f'{self.FUNCTION_NAME_BASE}{self.battle_count}', level=2)
        prev = self.battle_count
        result = False
        for _ in range(10):
            try:
                result = self.battle_function()
                break
            except MapEnemyMoved:
                if self.battle_count > prev:
                    result = True
                    break
                else:
                    continue

        if not result:
            logger.warning('ScriptError, No combat executed.')
            if self.config.ENABLE_EXCEPTION:
                raise ScriptError('No combat executed.')
            else:
                logger.warning(
                    'ScriptError, Withdrawing because enable_exception = no')
                self.withdraw()

        return result
예제 #7
0
    def __load_screenshot(self, screenshot, method):
        if method == 0:
            pass
        elif method == 1:
            screenshot = screenshot.replace(b'\r\n', b'\n')
        elif method == 2:
            screenshot = screenshot.replace(b'\r\r\n', b'\n')
        else:
            raise ScriptError(f'Unknown method to load screenshots: {method}')

        raw_compressed_data = self._ascreencap_reposition_byte_pointer(screenshot)

        # See headers in:
        # https://github.com/ClnViewer/Android-fast-screen-capture#streamimage-compressed---header-format-using
        compressed_data_header = np.frombuffer(raw_compressed_data[0:20], dtype=np.uint32)
        if compressed_data_header[0] != 828001602:
            compressed_data_header = compressed_data_header.byteswap()
            if compressed_data_header[0] != 828001602:
                text = f'aScreenCap header verification failure, corrupted image received. ' \
                    f'HEADER IN HEX = {compressed_data_header.tobytes().hex()}'
                logger.warning(text)
                raise AscreencapError(text)

        _, uncompressed_size, _, width, height = compressed_data_header
        channel = 3
        data = lz4.block.decompress(raw_compressed_data[20:], uncompressed_size=uncompressed_size)

        image = np.frombuffer(data, dtype=np.uint8)
        # Equivalent to cv2.imdecode()
        shape = image.shape[0]
        image = image[shape - width * height * channel:].reshape(height, width, channel)
        image = cv2.flip(image, 0)

        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        return image
예제 #8
0
    def screenshot_interval_set(self, interval=None):
        """
        Args:
            interval (int, float, str):
                Minimum interval between 2 screenshots in seconds.
                Or None for Optimization_ScreenshotInterval, 'combat' for Optimization_CombatScreenshotInterval
        """
        if interval is None:
            origin = self.config.Optimization_ScreenshotInterval
            interval = limit_in(origin, 0.1, 0.3)
            if interval != origin:
                logger.warning(
                    f'Optimization.ScreenshotInterval {origin} is revised to {interval}'
                )
                self.config.Optimization_ScreenshotInterval = interval
        elif interval == 'combat':
            origin = self.config.Optimization_CombatScreenshotInterval
            interval = limit_in(origin, 0.3, 1.0)
            if interval != origin:
                logger.warning(
                    f'Optimization.CombatScreenshotInterval {origin} is revised to {interval}'
                )
                self.config.Optimization_CombatScreenshotInterval = interval
        elif isinstance(interval, (int, float)):
            # No limitation for manual set in code
            pass
        else:
            logger.warning(f'Unknown screenshot interval: {interval}')
            raise ScriptError(f'Unknown screenshot interval: {interval}')

        if interval != self._screenshot_interval.limit:
            logger.info(f'Screenshot interval set to {interval}s')
            self._screenshot_interval.limit = interval
    def execute_a_battle(self):
        func = self.FUNCTION_NAME_BASE + 'default'
        for extra_battle in range(10):
            if hasattr(
                    self, self.FUNCTION_NAME_BASE +
                    str(self.battle_count - extra_battle)):
                func = self.FUNCTION_NAME_BASE + str(self.battle_count -
                                                     extra_battle)
                break

        logger.hr(f'{self.FUNCTION_NAME_BASE}{self.battle_count}', level=2)
        logger.info(f'Using function: {func}')
        func = self.__getattribute__(func)

        result = func()
        if not result:
            logger.warning('ScriptError, No combat executed.')
            if self.config.ENABLE_EXCEPTION:
                raise ScriptError('No combat executed.')
            else:
                logger.warning(
                    'ScriptError, Withdrawing because enable_exception = no')
                self.withdraw()

        return result
예제 #10
0
    def _handle_orientated_image(self, image):
        """
        Args:
            image (np.ndarray):

        Returns:
            np.ndarray:
        """
        width, height = image_size(self.image)
        if width == 1280 and height == 720:
            return image

        # Rotate screenshots only when they're not 1280x720
        if self.orientation == 0:
            pass
        elif self.orientation == 1:
            image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
        elif self.orientation == 2:
            image = cv2.rotate(image, cv2.ROTATE_180)
        elif self.orientation == 3:
            image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
        else:
            raise ScriptError(
                f'Invalid device orientation: {self.orientation}')

        return image
예제 #11
0
    def task_call(self, task, force_call=True):
        """
        Call another task to run.

        That task will run when current task finished.
        But it might not be run because:
        - Other tasks should run first according to SCHEDULER_PRIORITY
        - Task is disabled by user

        Args:
            task (str): Task name to call, such as `Restart`
            force_call (bool):

        Returns:
            bool: If called.
        """
        if deep_get(self.data, keys=f"{task}.Scheduler.NextRun",
                    default=None) is None:
            raise ScriptError(
                f"Task to call: `{task}` does not exist in user config")

        if force_call or deep_get(
                self.data, keys=f"{task}.Scheduler.Enable", default=False):
            logger.info(f"Task call: {task}")
            self.modified[f"{task}.Scheduler.NextRun"] = datetime.now(
            ).replace(microsecond=0)
            self.modified[f"{task}.Scheduler.Enable"] = True
            self.update()
            return True
        else:
            logger.info(
                f"Task call: {task} (skipped because disabled by user)")
            return False
예제 #12
0
    def run(self, name='', mode='', total=0):
        """
        Args:
            name (str): Raid name, such as 'raid_20200624'
            mode (str): Raid mode, such as 'hard', 'normal', 'easy'
            total (int): Total run count
        """
        name = name if name else self.config.Campaign_Event
        mode = mode if mode else self.config.Raid_Mode
        if not name or not mode:
            raise ScriptError(
                f'RaidRun arguments unfilled. name={name}, mode={mode}')

        self.run_count = 0
        self.run_limit = self.config.StopCondition_RunCount
        while 1:
            # End
            if total and self.run_count == total:
                break
            if self.event_time_limit_triggered():
                self.config.task_stop()

            # Log
            logger.hr(f'{name}_{mode}', level=2)
            if self.config.StopCondition_RunCount > 0:
                logger.info(
                    f'Count remain: {self.config.StopCondition_RunCount}')
            else:
                logger.info(f'Count: {self.run_count}')

            # End
            if self.triggered_stop_condition():
                break

            # UI ensure
            self.ui_ensure(page_raid)

            # Run
            try:
                self.raid_execute_once(mode=mode, raid=name)
            except OilExhausted:
                logger.hr('Triggered stop condition: Oil limit')
                self.config.task_delay(minute=(120, 240))
                break
            except ScriptEnd as e:
                logger.hr('Script end')
                logger.info(str(e))
                break

            # After run
            self.run_count += 1
            if self.config.StopCondition_RunCount:
                self.config.StopCondition_RunCount -= 1
            # End
            if self.triggered_stop_condition():
                break
            # Scheduler
            if self.config.task_switched():
                self.config.task_stop()
예제 #13
0
 def find_tesseract_tool(self):
     """
     Returns:
         pyocr tesseract wrapper module
     """
     ocr_tool = pyocr.get_available_tools()
     if len(ocr_tool) == 0:
         raise ScriptError(
             'No ocr-tool found, please install tesseract by yourself and make sure to set correct env vars.'
         )
     ocr_tool = ocr_tool[0]
     ocr_langs = ocr_tool.get_available_languages()
     if 'jpn' not in ocr_langs:
         raise ScriptError(
             'No jpn found in tesseract langs, please install japanese data files.'
         )
     return ocr_tool
예제 #14
0
    def dock_filter_set(self, category, type, enable):
        key = f'filter_{category}_{type}'

        try:
            obj = globals()[key]
            obj.set('on' if enable else 'off', main=self)
        except KeyError:
            raise ScriptError(f'{key} filter switch object does not exist in module/retire/dock.py')
예제 #15
0
    def globe_goto(self, zone, types=('SAFE', 'DANGEROUS'), refresh=False, stop_if_safe=False):
        """
        Goto another zone in OS.

        Args:
            zone (str, int, Zone): Name in CN/EN/JP/TW, zone id, or Zone instance.
            types (tuple[str], list[str], str): Zone types, or a list of them.
                Available types: DANGEROUS, SAFE, OBSCURE, ABYSSAL, STRONGHOLD.
                Try the the first selection in type list, if not available, try the next one.
            refresh (bool): If already at target zone,
                set false to skip zone switching,
                set true to re-enter current zone to refresh.
            stop_if_safe (bool): Return false if zone is SAFE.

        Returns:
            bool: If zone switched.

        Pages:
            in: IN_MAP or IN_GLOBE
            out: IN_MAP
        """
        zone = self.name_to_zone(zone)
        logger.hr(f'Globe goto: {zone}')
        if self.zone == zone:
            if refresh:
                logger.info('Goto another zone to refresh current zone')
                return self.globe_goto(self.zone_nearest_azur_port(self.zone),
                                       types=('SAFE', 'DANGEROUS'), refresh=False)
            else:
                logger.info('Already at target zone')
                return False
        # MAP_EXIT
        if self.is_in_special_zone():
            self.map_exit()
        # IN_MAP
        if self.is_in_map():
            self.os_map_goto_globe()
        # IN_GLOBE
        if not self.is_in_globe():
            logger.warning('Trying to move in globe, but not in os globe map')
            raise ScriptError('Trying to move in globe, but not in os globe map')
        # self.ensure_no_zone_pinned()
        self.globe_update()
        self.globe_focus_to(zone)
        if stop_if_safe:
            if self.zone_has_safe():
                logger.info('Zone is safe, stopped')
                self.ensure_no_zone_pinned()
                return False
        self.zone_type_select(types=types)
        self.globe_enter(zone)
        # IN_MAP
        if hasattr(self, 'zone'):
            del self.zone
        self.zone_init()
        # self.map_init()
        return True
예제 #16
0
 def _load_screenshot(self, screenshot, method):
     if method == 0:
         return Image.open(BytesIO(screenshot)).convert('RGB')
     elif method == 1:
         return Image.open(BytesIO(screenshot.replace(b'\r\n', b'\n'))).convert('RGB')
     elif method == 2:
         return Image.open(BytesIO(screenshot.replace(b'\r\r\n', b'\n'))).convert('RGB')
     else:
         raise ScriptError(f'Unknown method to load screenshots: {method}')
예제 #17
0
 def __load_screenshot(self, screenshot, method):
     if method == 0:
         return screenshot
     elif method == 1:
         return screenshot.replace(b'\r\n', b'\n')
     elif method == 2:
         return screenshot.replace(b'\r\r\n', b'\n')
     else:
         raise ScriptError(f'Unknown method to load screenshots: {method}')
예제 #18
0
    def task_delay(self,
                   success=None,
                   server_update=None,
                   target=None,
                   minute=None):
        """
        Set Scheduler.NextRun
        Should set at least one arguments.
        If multiple arguments are set, use the nearest.

        Args:
            success (bool):
                If True, delay Scheduler.SuccessInterval
                If False, delay Scheduler.FailureInterval
            server_update (bool, list, str):
                If True, delay to nearest Scheduler.ServerUpdate
                If type is list or str, delay to such server update
            target (datetime.datetime, str, list):
                Delay to such time.
            minute (int, float, tuple):
                Delay several minutes.
        """
        def ensure_delta(delay):
            return timedelta(seconds=int(ensure_time(delay, precision=3) * 60))

        run = []
        if success is not None:
            interval = (self.Scheduler_SuccessInterval
                        if success else self.Scheduler_FailureInterval)
            run.append(datetime.now() + ensure_delta(interval))
        if server_update is not None:
            if server_update is True:
                server_update = self.Scheduler_ServerUpdate
            run.append(get_server_next_update(server_update))
        if target is not None:
            target = [target] if not isinstance(target, list) else target
            target = nearest_future(target)
            run.append(target)
        if minute is not None:
            run.append(datetime.now() + ensure_delta(minute))

        if len(run):
            run = min(run).replace(microsecond=0)
            kv = dict_to_kv(
                {
                    "success": success,
                    "server_update": server_update,
                    "target": target,
                    "minute": minute,
                },
                allow_none=False,
            )
            logger.info(f"Delay task `{self.task.command}` to {run} ({kv})")
            self.Scheduler_NextRun = run
        else:
            raise ScriptError(
                "Missing argument in delay_next_run, should set at least one")
예제 #19
0
 def _api(self):
     method = self.config.DropRecord_API
     if method == 'default':
         return 'https://azurstats.lyoko.io/api/upload/'
     elif method == 'cn_gz_reverse_proxy':
         return 'https://service-rjfzwz8i-1301182309.gz.apigw.tencentcs.com/api/upload'
     elif method == 'cn_sh_reverse_proxy':
         return 'https://service-nlvjetab-1301182309.sh.apigw.tencentcs.com/api/upload'
     else:
         logger.critical('Invalid upload API, please check your settings')
         raise ScriptError('Invalid upload API')
예제 #20
0
    def _storage_item_to_template(item):
        """
        Args:
            item (str): 'OBSCURE' or 'ABYSSAL'.

        Returns:
            Template:
        """
        if item == 'OBSCURE':
            return TEMPLATE_STORAGE_OBSCURE
        elif item == 'ABYSSAL':
            return TEMPLATE_STORAGE_ABYSSAL
        else:
            raise ScriptError(f'Unknown storage item: {item}')
예제 #21
0
def raid_name_shorten(name):
    """
    Args:
        name (str): Raid name, such as raid_20200624, raid_20210708.

    Returns:
        str: Prefix of button name, such as ESSEX, SURUGA.
    """
    if name == 'raid_20200624':
        return 'ESSEX'
    elif name == 'raid_20210708':
        return 'SURUGA'
    else:
        raise ScriptError(f'Unknown raid name: {name}')
예제 #22
0
def raid_entrance(raid, mode):
    """
    Args:
        raid (str): Raid name, such as raid_20200624, raid_20210708.
        mode (str): easy, normal, hard

    Returns:
        Button:
    """
    key = f'{raid_name_shorten(raid)}_RAID_{mode.upper()}'
    try:
        return globals()[key]
    except KeyError:
        raise ScriptError(f'Raid entrance asset not exists: {key}')
예제 #23
0
    def get_current_zone(self):
        """
        Returns:
            Zone:
        """
        if not self.is_in_map():
            logger.warning('Trying to get zone name, but not in OS map')
            raise ScriptError('Trying to get zone name, but not in OS map')

        name = self.get_zone_name()
        logger.info(f'Map name processed: {name}')
        self.zone = self.name_to_zone(name)
        logger.attr('Zone', self.zone)
        return self.zone
예제 #24
0
    def zone_select(self, hazard_level):
        """
        Similar to `self.zone.select(**kwargs)`, but delete zones in region 5.

        Args:
            hazard_level: 1-6, or 10 for center zones.

        Returns:
            SelectedGrids: SelectedGrids containing zone objects.
        """
        if 1 <= hazard_level <= 6:
            return self.zones.select(hazard_level=hazard_level).delete(self.zones.select(region=5))
        elif hazard_level == 10:
            return self.zones.select(region=5)
        else:
            raise ScriptError(f'Invalid hazard_level of zones: {hazard_level}')
예제 #25
0
    def click_record_check(self, button):
        """
        Args:
            button (button.Button): AzurLane Button instance.

        Returns:
            bool:
        """
        if sum([1 if str(prev) == str(button) else 0 for prev in self.click_record]) >= 12:
            logger.warning(f'Too many click for a button: {button}')
            logger.info(f'History click: {[str(prev) for prev in self.click_record]}')
            raise ScriptError(f'Too many click for a button: {button}')
        else:
            self.click_record.append(str(button))

        return False
예제 #26
0
    def _retire_handler(self, mode=None):
        """
        Args:
            mode (str): `one_click_retire` or `old_retire`

        Returns:
            int: Amount of retired ships

        Pages:
            in: IN_RETIREMENT_CHECK
            out: the page before retirement popup
        """
        if mode is None:
            mode = self.config.Retirement_RetireMode

        if mode == 'one_click_retire':
            total = self.retire_ships_one_click()
            if not total:
                logger.warning(
                    'No ship retired, trying to reset dock filter and disable favourite, then retire again'
                )
                self.dock_filter_set()
                self.dock_favourite_set(False)
                total = self.retire_ships_one_click()
            if not total:
                logger.critical('No ship retired')
                logger.critical(
                    'Please configure your one-click-retire in game, '
                    'make sure it can select ships to retire')
                raise RequestHumanTakeover
        elif mode == 'old_retire':
            self.handle_dock_cards_loading()
            total = self.retire_ships_old()
            if not total:
                logger.critical('No ship retired')
                logger.critical(
                    'Please configure your retirement settings in Alas, '
                    'make sure it can select ships to retire')
                raise RequestHumanTakeover
        else:
            raise ScriptError(
                f'Unknown retire mode: {self.config.Retirement_RetireMode}')

        self._retirement_quit()
        self.config.DOCK_FULL_TRIGGERED = True

        return total
예제 #27
0
    def get_data(self, status):
        """
        Args:
            status (str):

        Returns:
            dict: Dictionary in add_status

        Raises:
            ScriptError: If status invalid
        """
        for row in self.status_list:
            if row['status'] == status:
                return row

        logger.warning(f'Switch {self.name} received an invalid status {status}')
        raise ScriptError(f'Switch {self.name} received an invalid status {status}')
예제 #28
0
    def _tactical_books_get(self, skip_first_screenshot=True):
        """
        Get books. Handle loadings, wait 10 times at max.
        When TACTICAL_CLASS_START appears, game may stuck in loading, wait and retry detection.
        If loading still exists, raise ScriptError.

        Returns:
            BookGroup:

        Pages:
            in: TACTICAL_CLASS_START
            out: TACTICAL_CLASS_START
        """
        prev = SelectedGrids([])
        for n in range(1, 16):
            if skip_first_screenshot:
                skip_first_screenshot = False
            else:
                self.device.screenshot()

            self.handle_info_bar(
            )  # info_bar appears when get ship in Launch Ceremony commissions
            if not self.appear(TACTICAL_CLASS_START, offset=(30, 30)):
                logger.info('Not in TACTICAL_CLASS_START anymore, exit')
                return False

            books = SelectedGrids([
                Book(self.device.image, button)
                for button in BOOKS_GRID.buttons
            ]).select(valid=True)
            self.books = books
            logger.attr('Book_count', books.count)
            logger.attr('Books', str(books))

            # End
            if books and books.count == prev.count:
                return books
            else:
                prev = books
                if n % 3 == 0:
                    self.device.sleep(3)
                continue

        logger.warning('No book found.')
        raise ScriptError('No book found, after 15 attempts.')
예제 #29
0
    def run(self):
        logger.hr(self.ENTRANCE, level=2)
        self.handle_spare_fleet()
        self.ENTRANCE.area = self.ENTRANCE.button
        self.enter_map(self.ENTRANCE, mode=self.config.CAMPAIGN_MODE)
        self.handle_map_fleet_lock()
        self.handle_fleet_reverse()
        self.map_init(self.MAP)

        for _ in range(20):
            try:
                self.execute_a_battle()
            except CampaignEnd:
                logger.hr('Campaign end')
                return True

        logger.warning('Battle function exhausted.')
        raise ScriptError('Battle function exhausted.')
예제 #30
0
def raid_ocr(raid, mode):
    """
    Args:
        raid (str): Raid name, such as raid_20200624, raid_20210708.
        mode (str): easy, normal, hard

    Returns:
        RaidCounter:
    """
    raid = raid_name_shorten(raid)
    key = f'{raid}_OCR_REMAIN_{mode.upper()}'
    try:
        button = globals()[key]
        if raid == 'ESSEX':
            return RaidCounter(button, letter=(57, 52, 255), threshold=128)
        elif raid == 'SURUGA':
            return RaidCounter(button, letter=(49, 48, 49), threshold=128)
    except KeyError:
        raise ScriptError(f'Raid entrance asset not exists: {key}')