|
| 1 | +from jellyfish import levenshtein_distance |
| 2 | + |
| 3 | +from module.base.timer import Timer |
| 4 | +from module.combat.assets import GET_ITEMS_1 |
| 5 | +import module.config.server as server |
| 6 | +from module.base.button import ButtonGrid |
| 7 | +from module.base.decorator import cached_property, del_cached_property |
| 8 | +from module.island.assets import * |
| 9 | +from module.island.data import DIC_ISLAND_TASK |
| 10 | +from module.island.ui import IslandUI |
| 11 | +from module.island_handler.production_planner import item_mapping_to_yaml, load_item_mapping, normalize_item_keys |
| 12 | +from module.logger import logger |
| 13 | +from module.map_detection.utils import Points |
| 14 | +from module.ocr.ocr import Ocr |
| 15 | +from module.ui.navbar import Navbar |
| 16 | +from module.ui.page import page_island_season |
| 17 | +from module.ui.scroll import Scroll |
| 18 | + |
| 19 | + |
| 20 | +DETECT_AREA = (42, 171, 1206, 604) |
| 21 | +ICON_AREA = (21, 133, 225, 195) |
| 22 | +TAB_DELTA = (395, 230) |
| 23 | +TAB_SIZE = (376, 211) |
| 24 | +NAME_AREA = (29, 25, 200, 53) |
| 25 | +ISLAND_SEASON_TASK_SCROLL = Scroll( |
| 26 | + ISLAND_SEASON_TASK_SCROLL_AREA.button, |
| 27 | + color=(128, 128, 128), |
| 28 | + name="ISLAND_SEASON_TASK_SCROLL" |
| 29 | +) |
| 30 | + |
| 31 | +class IslandSeasonTask(IslandUI): |
| 32 | + @cached_property |
| 33 | + def _island_season_bottom_navbar(self): |
| 34 | + """ |
| 35 | + 6 options: |
| 36 | + homepage, |
| 37 | + pt_reward, |
| 38 | + season_task, |
| 39 | + season_shop, |
| 40 | + season_rank, |
| 41 | + season_history |
| 42 | + """ |
| 43 | + island_season_bottom_navbar = ButtonGrid( |
| 44 | + origin=(14, 677), delta=(213, 0), |
| 45 | + button_shape=(186, 33), grid_shape=(6, 1), |
| 46 | + name='ISLAND_SEASON_BOTTOM_NAVBAR' |
| 47 | + ) |
| 48 | + return Navbar(grids=island_season_bottom_navbar, |
| 49 | + active_color=(237, 237, 237), |
| 50 | + inactive_color=(65, 78, 96), |
| 51 | + active_count=500, |
| 52 | + inactive_count=500) |
| 53 | + |
| 54 | + def island_season_bottom_navbar_ensure(self, left=None, right=None): |
| 55 | + """ |
| 56 | + Args: |
| 57 | + left (int): |
| 58 | + 1 for homepage, |
| 59 | + 2 for pt_reward, |
| 60 | + 3 for season_task, |
| 61 | + 4 for season_shop, |
| 62 | + 5 for season_rank, |
| 63 | + 6 for season_history |
| 64 | + right (int): |
| 65 | + 1 for season_history, |
| 66 | + 2 for season_rank, |
| 67 | + 3 for season_shop, |
| 68 | + 4 for season_task, |
| 69 | + 5 for pt_reward, |
| 70 | + 6 for homepage |
| 71 | +
|
| 72 | + """ |
| 73 | + if self._island_season_bottom_navbar.set(self, left=left, right=right): |
| 74 | + return True |
| 75 | + return False |
| 76 | + |
| 77 | + def _get_icons(self): |
| 78 | + area = (DETECT_AREA[0] + ICON_AREA[0], DETECT_AREA[1] + ICON_AREA[1] - NAME_AREA[1], DETECT_AREA[2], DETECT_AREA[3]) |
| 79 | + image = self.image_crop(area, copy=True) |
| 80 | + icons = TEMPLATE_ISLAND_SEASON_REWARD.match_multi(image, similarity=0.7, threshold=5) |
| 81 | + icons = Points([(0., icon.area[1]) for icon in icons]).group(threshold=5) |
| 82 | + logger.attr('icons_count', len(icons)) |
| 83 | + return icons |
| 84 | + |
| 85 | + @cached_property |
| 86 | + def season_task_grid(self): |
| 87 | + for _ in self.loop(timeout=3): |
| 88 | + grid = self.get_season_task_grid() |
| 89 | + if len(grid.buttons) >= 3: |
| 90 | + return grid |
| 91 | + return grid |
| 92 | + |
| 93 | + def get_season_task_grid(self): |
| 94 | + rows = self._get_icons() |
| 95 | + count = len(rows) |
| 96 | + if count >= 2: |
| 97 | + y_list = rows[:, 1] |
| 98 | + y1, y2 = y_list[0], y_list[-1] |
| 99 | + origin_y = min(y1, y2) + DETECT_AREA[1] - NAME_AREA[1] |
| 100 | + delta_y = TAB_DELTA[1] |
| 101 | + elif count < 1: |
| 102 | + logger.warning('No icons detected, assume at top') |
| 103 | + origin_y = DETECT_AREA[1] - NAME_AREA[1] |
| 104 | + delta_y = TAB_DELTA[1] |
| 105 | + else: |
| 106 | + origin_y = rows[0][1] + DETECT_AREA[1] - NAME_AREA[1] |
| 107 | + delta_y = TAB_DELTA[1] |
| 108 | + |
| 109 | + season_task_grid = ButtonGrid( |
| 110 | + origin=(DETECT_AREA[0], origin_y), |
| 111 | + delta=(TAB_DELTA[0], delta_y), |
| 112 | + button_shape=TAB_SIZE, |
| 113 | + grid_shape=(3, count), name='SEASON_TASK_GRID' |
| 114 | + ) |
| 115 | + return season_task_grid |
| 116 | + |
| 117 | + @cached_property |
| 118 | + def task_names(self): |
| 119 | + return self.get_task_codename() |
| 120 | + |
| 121 | + @property |
| 122 | + def task_name_ocr(self): |
| 123 | + if server.server == 'jp': |
| 124 | + lang = 'jp' |
| 125 | + elif server.server == 'en': |
| 126 | + lang = 'azur_lane' |
| 127 | + else: |
| 128 | + lang = 'cnocr' |
| 129 | + ocr = Ocr([], lang=lang, letter=(57, 58, 60), name='task_name_ocr') |
| 130 | + return ocr |
| 131 | + |
| 132 | + def get_task_codename(self): |
| 133 | + name_grid = self.season_task_grid.crop(NAME_AREA, name='TASK_NAME_GRID') |
| 134 | + name_images = [self.image_crop(button.area, copy=True) for button in name_grid.buttons] |
| 135 | + names = self.task_name_ocr.ocr(name_images, direct_ocr=True) |
| 136 | + codenames = [self.task_name_to_codename(name) for name in names] |
| 137 | + logger.attr('Codenames', codenames) |
| 138 | + return codenames |
| 139 | + |
| 140 | + def task_name_to_codename(self, name): |
| 141 | + min_distance = float('inf') |
| 142 | + code = None |
| 143 | + corrected_name = None |
| 144 | + for key, item in DIC_ISLAND_TASK.items(): |
| 145 | + distance = levenshtein_distance(name, item['name'][server.server]) |
| 146 | + if distance < min_distance: |
| 147 | + min_distance = distance |
| 148 | + code = key |
| 149 | + corrected_name = item['name'][server.server] |
| 150 | + if name != corrected_name: |
| 151 | + logger.warning(f'Task name corrected: {name} -> {corrected_name}') |
| 152 | + return code |
| 153 | + |
| 154 | + def handle_island_get_items(self): |
| 155 | + if self.appear(GET_ITEMS_1, offset=(20, 100), interval=3): |
| 156 | + self.device.click(ISLAND_SEASON_TASK_RECEIVE_ALL) |
| 157 | + return True |
| 158 | + if self.has_white_band(): |
| 159 | + if self.appear(page_island_season.check_button): |
| 160 | + return False |
| 161 | + self.device.click(ISLAND_SEASON_TASK_RECEIVE_ALL) |
| 162 | + return True |
| 163 | + return False |
| 164 | + |
| 165 | + def receive_all_reward(self): |
| 166 | + clicked = False |
| 167 | + confirm_timer = Timer(3, count=6) |
| 168 | + for _ in self.loop(skip_first=False): |
| 169 | + if self.appear_then_click(ISLAND_SEASON_TASK_RECEIVE_ALL, interval=2, offset=(20, 20)): |
| 170 | + clicked = True |
| 171 | + confirm_timer.reset() |
| 172 | + continue |
| 173 | + if self.handle_island_additional(): |
| 174 | + confirm_timer.reset() |
| 175 | + continue |
| 176 | + if confirm_timer.reached(): |
| 177 | + if clicked: |
| 178 | + logger.info('Receive all rewards confirmed') |
| 179 | + else: |
| 180 | + logger.info('No rewards to receive') |
| 181 | + break |
| 182 | + |
| 183 | + def scan_all(self): |
| 184 | + ISLAND_SEASON_TASK_SCROLL.set_top(main=self, skip_first_screenshot=False) |
| 185 | + logger.hr('scanning season tasks') |
| 186 | + unfinished_tasks = [] |
| 187 | + early_stop = False |
| 188 | + while 1: |
| 189 | + for task_id, button in zip(self.task_names, self.season_task_grid.buttons): |
| 190 | + image = self.image_crop(button.area, copy=True) |
| 191 | + if TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.match(image): |
| 192 | + early_stop = True |
| 193 | + break |
| 194 | + else: |
| 195 | + unfinished_tasks.append(task_id) |
| 196 | + if early_stop: |
| 197 | + logger.info(f'Detect obtained task, early stop scanning') |
| 198 | + break |
| 199 | + if ISLAND_SEASON_TASK_SCROLL.at_bottom(main=self): |
| 200 | + break |
| 201 | + else: |
| 202 | + ISLAND_SEASON_TASK_SCROLL.next_page(main=self) |
| 203 | + del_cached_property(self, 'season_task_grid') |
| 204 | + del_cached_property(self, 'task_names') |
| 205 | + continue |
| 206 | + logger.info(f'Unfinished tasks: {unfinished_tasks}') |
| 207 | + return unfinished_tasks |
| 208 | + |
| 209 | + def run(self): |
| 210 | + self.ui_ensure(page_island_season) |
| 211 | + self.island_season_bottom_navbar_ensure(left=3) |
| 212 | + self.receive_all_reward() |
| 213 | + yaml_text = self.config.cross_get("IslandSeasonTask.IslandSeasonTask.TaskTarget", "{}") |
| 214 | + old_target = normalize_item_keys(load_item_mapping(yaml_text, config_name='TaskTarget')) |
| 215 | + new_target = {} |
| 216 | + unfinished_tasks = self.scan_all() |
| 217 | + for task_id in unfinished_tasks: |
| 218 | + target = DIC_ISLAND_TASK[task_id]['target'] |
| 219 | + if target: |
| 220 | + item_id = list(target.keys())[0] |
| 221 | + new_target[item_id] = new_target.get(item_id, 0) + target[item_id] |
| 222 | + new_target = normalize_item_keys(new_target) |
| 223 | + if new_target != old_target: |
| 224 | + yaml_text = item_mapping_to_yaml(new_target, use_item_name=True) |
| 225 | + self.config.cross_set("IslandSeasonTask.IslandSeasonTask.TaskTarget", yaml_text) |
| 226 | + from module.island_handler.production_planner import IslandProductionPlanner |
| 227 | + IslandProductionPlanner(self.config, self.device).run() |
| 228 | + self.config.task_delay(server_update=True) |
0 commit comments