Skip to content

Commit 5dc0cb9

Browse files
committed
Add: [JP] island season task scanner
1 parent 4f6c94a commit 5dc0cb9

9 files changed

Lines changed: 237 additions & 1 deletion

alas.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,10 @@ def island_collect(self):
419419
from module.island.collect import IslandCollect
420420
IslandCollect(config=self.config, device=self.device).run()
421421

422+
def island_season_task(self):
423+
from module.island.season_task import IslandSeasonTask
424+
IslandSeasonTask(config=self.config, device=self.device).run()
425+
422426
def daemon(self):
423427
from module.daemon.daemon import AzurLaneDaemon
424428
AzurLaneDaemon(config=self.config, device=self.device, task="Daemon").run()
7.42 KB
Loading
7.7 KB
Loading
8.3 KB
Loading
5.51 KB
Loading
5.51 KB
Loading

module/config/config_manual.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def SERVER(self):
2323
> OpsiDaily > OpsiShop > OpsiVoucher
2424
> OpsiAbyssal > OpsiStronghold > OpsiObscure > OpsiArchive
2525
> IslandProduction
26-
> IslandFreebie > IslandCollect
26+
> IslandFreebie > IslandCollect > IslandSeasonTask
2727
> Daily > Hard > OpsiAshBeacon > OpsiAshAssist > OpsiMonthBoss
2828
> Sos > EventSp > EventA > EventB > EventC > EventD
2929
> RaidDaily > CoalitionSp > WarArchives > MaritimeEscort

module/island/assets.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,9 @@
2424
ISLAND_PRODUCTION_RECEIVE = Button(area={'cn': (591, 549, 630, 570), 'en': (591, 549, 630, 570), 'jp': (591, 549, 630, 570), 'tw': (591, 549, 630, 570)}, color={'cn': (83, 197, 255), 'en': (83, 197, 255), 'jp': (83, 197, 255), 'tw': (83, 197, 255)}, button={'cn': (591, 549, 630, 570), 'en': (591, 549, 630, 570), 'jp': (591, 549, 630, 570), 'tw': (591, 549, 630, 570)}, file={'cn': './assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png', 'en': './assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png', 'jp': './assets/jp/island/ISLAND_PRODUCTION_RECEIVE.png', 'tw': './assets/cn/island/ISLAND_PRODUCTION_RECEIVE.png'})
2525
ISLAND_PRODUCTION_RERUN = Button(area={'cn': (596, 540, 684, 565), 'en': (596, 540, 684, 565), 'jp': (596, 540, 684, 565), 'tw': (596, 540, 684, 565)}, color={'cn': (247, 211, 142), 'en': (247, 211, 142), 'jp': (247, 211, 142), 'tw': (247, 211, 142)}, button={'cn': (596, 540, 684, 565), 'en': (596, 540, 684, 565), 'jp': (596, 540, 684, 565), 'tw': (596, 540, 684, 565)}, file={'cn': './assets/cn/island/ISLAND_PRODUCTION_RERUN.png', 'en': './assets/cn/island/ISLAND_PRODUCTION_RERUN.png', 'jp': './assets/jp/island/ISLAND_PRODUCTION_RERUN.png', 'tw': './assets/cn/island/ISLAND_PRODUCTION_RERUN.png'})
2626
ISLAND_PRODUCTION_SELECT_CHARACTER = Button(area={'cn': (579, 315, 607, 348), 'en': (579, 315, 607, 348), 'jp': (579, 315, 607, 348), 'tw': (579, 315, 607, 348)}, color={'cn': (246, 246, 245), 'en': (246, 246, 245), 'jp': (246, 246, 245), 'tw': (246, 246, 245)}, button={'cn': (579, 315, 607, 348), 'en': (579, 315, 607, 348), 'jp': (579, 315, 607, 348), 'tw': (579, 315, 607, 348)}, file={'cn': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png', 'en': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png', 'jp': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png', 'tw': './assets/cn/island/ISLAND_PRODUCTION_SELECT_CHARACTER.png'})
27+
ISLAND_SEASON_TASK_RECEIVE_ALL = Button(area={'cn': (1059, 618, 1093, 646), 'en': (1059, 618, 1093, 646), 'jp': (1059, 618, 1093, 646), 'tw': (1059, 618, 1093, 646)}, color={'cn': (247, 220, 119), 'en': (247, 220, 119), 'jp': (247, 220, 119), 'tw': (247, 220, 119)}, button={'cn': (1059, 618, 1093, 646), 'en': (1059, 618, 1093, 646), 'jp': (1059, 618, 1093, 646), 'tw': (1059, 618, 1093, 646)}, file={'cn': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png', 'en': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png', 'jp': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png', 'tw': './assets/cn/island/ISLAND_SEASON_TASK_RECEIVE_ALL.png'})
28+
ISLAND_SEASON_TASK_SCROLL_AREA = Button(area={'cn': (1236, 171, 1237, 604), 'en': (1236, 171, 1237, 604), 'jp': (1236, 171, 1237, 604), 'tw': (1236, 171, 1237, 604)}, color={'cn': (191, 195, 192), 'en': (191, 195, 192), 'jp': (191, 195, 192), 'tw': (191, 195, 192)}, button={'cn': (1236, 171, 1237, 604), 'en': (1236, 171, 1237, 604), 'jp': (1236, 171, 1237, 604), 'tw': (1236, 171, 1237, 604)}, file={'cn': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png', 'en': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png', 'jp': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png', 'tw': './assets/cn/island/ISLAND_SEASON_TASK_SCROLL_AREA.png'})
2729
TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_ANCHOR_ICON.png'})
2830
TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_PRODUCTION_SLOT_EMPTY.png'})
31+
TEMPLATE_ISLAND_SEASON_REWARD = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_SEASON_REWARD.png'})
32+
TEMPLATE_ISLAND_SEASON_TASK_OBTAINED = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png', 'jp': './assets/jp/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.png'})

module/island/season_task.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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

Comments
 (0)