Skip to content

Commit 538f723

Browse files
committed
Add: [JP] island season task scanner
1 parent 0541829 commit 538f723

File tree

6 files changed

+255
-0
lines changed

6 files changed

+255
-0
lines changed
4.33 KB
Loading
5.51 KB
Loading
5.51 KB
Loading

module/island/assets.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from module.base.button import Button
2+
from module.base.template import Template
3+
4+
# This file was automatically generated by dev_tools/button_extract.py.
5+
# Don't modify it manually.
6+
7+
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': (186, 192, 191), 'en': (186, 192, 191), 'jp': (186, 192, 191), 'tw': (186, 192, 191)}, 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'})
8+
TEMPLATE_ISLAND_SEASON_TASK_ICON = Template(file={'cn': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_ICON.png', 'en': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_ICON.png', 'jp': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_ICON.png', 'tw': './assets/cn/island/TEMPLATE_ISLAND_SEASON_TASK_ICON.png'})
9+
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: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import module.config.server as server
2+
from module.base.button import ButtonGrid
3+
from module.base.decorator import cached_property, del_cached_property, Config
4+
from module.base.timer import Timer
5+
from module.base.utils import area_offset
6+
from module.island.assets import *
7+
from module.island.data import DIC_ISLAND_SEASONAL_TASK
8+
from module.island.ui import IslandUI, ISLAND_SEASON_TASK_SCROLL
9+
from module.logger import logger
10+
from module.map_detection.utils import Points
11+
from module.ocr.ocr import DigitCounter, Ocr
12+
from module.ui.page import page_island_season
13+
14+
if server.server == 'cn':
15+
lang = 'cnocr'
16+
elif server.server == 'en':
17+
lang = 'azur_lane'
18+
else:
19+
lang = server.server
20+
TASK_NAME_OCR = Ocr([], lang=lang, letter=(64, 64, 64), name='TASK_NAME_OCR')
21+
TASK_COUNTER_OCR = DigitCounter([], letter=(128, 128, 128), name='TASK_COUNTER_OCR')
22+
23+
class IslandSeasonTaskScanner(IslandUI):
24+
def _get_bars(self):
25+
"""
26+
Returns:
27+
np.array: [[x1, y1], [x2, y2]], location of the bar icon upper left corner.
28+
"""
29+
area = (43, 178, 875, 478)
30+
image = self.image_crop(area, copy=True)
31+
bars = TEMPLATE_ISLAND_SEASON_TASK_ICON.match_multi(image, threshold=5)
32+
bars = Points([(0., b.area[1]) for b in bars]).group(threshold=5)
33+
logger.attr('bars_icon', len(bars))
34+
return bars
35+
36+
def wait_until_bar_appear(self, skip_first_screenshot=False):
37+
"""
38+
After entering season task page,
39+
tasks are not loaded that fast,
40+
wait until any bar icon appears.
41+
"""
42+
confirm_timer = Timer(1.5, count=3).start()
43+
for _ in self.loop(skip_first=skip_first_screenshot):
44+
bars = self._get_bars()
45+
if len(bars):
46+
if confirm_timer.reached():
47+
return
48+
else:
49+
pass
50+
else:
51+
confirm_timer.reset()
52+
53+
@cached_property
54+
def task_grid(self):
55+
return self.task_bar_grid()
56+
57+
def task_bar_grid(self):
58+
"""
59+
Returns:
60+
ButtonGrid:
61+
"""
62+
bars = self._get_bars()
63+
count = len(bars)
64+
if count == 0:
65+
logger.warning('Unable to find bar icon, assume task list is at top')
66+
origin_y = 178
67+
delta_y = 229
68+
row = 2
69+
elif count == 1:
70+
y_list = bars[:, 1]
71+
# -16 to adjust the bar position to grid position
72+
origin_y = y_list[0] - 16 + 178
73+
delta_y = 229
74+
row = 1
75+
elif count == 2:
76+
y_list = bars[:, 1]
77+
origin_y = min(y_list) - 16 + 178
78+
delta_y = abs(y_list[1] - y_list[0])
79+
row = 2
80+
else:
81+
logger.warning(f'Too many bars found ({count}), assume max rows')
82+
y_list = bars[:, 1]
83+
origin_y = min(y_list) - 16 + 178
84+
delta_y = abs(y_list[1] - y_list[0])
85+
row = 2
86+
task_grid = ButtonGrid(
87+
origin=(43, origin_y), delta=(394, delta_y),
88+
button_shape=(375, 210), grid_shape=(3, row),
89+
name='SEASONAL_TASK_GRID'
90+
)
91+
return task_grid
92+
93+
@Config.when(SERVER='jp')
94+
def task_id_parse(self, string):
95+
string = string.replace('一', 'ー').replace('へ', 'ヘ')
96+
import jellyfish
97+
min_key = ''
98+
min_distance = 100
99+
for key, value in DIC_ISLAND_SEASONAL_TASK.items():
100+
distance = jellyfish.levenshtein_distance(value['name']['jp'], string)
101+
if distance < min_distance:
102+
min_distance = distance
103+
min_key = key
104+
if min_distance < 3:
105+
return min_key
106+
logger.warning(f'Unknown task name: {string}')
107+
return None
108+
109+
@Config.when(SERVER=None)
110+
def task_id_parse(self, string):
111+
for key, value in DIC_ISLAND_SEASONAL_TASK.items():
112+
if string == value['name'][server.server]:
113+
return key
114+
logger.warning(f'Unknown task name: {string}')
115+
return None
116+
117+
def predict(self, grid: ButtonGrid):
118+
"""
119+
Predicts all tasks in the given grid.
120+
121+
Args:
122+
grid (ButtonGrid):
123+
"""
124+
name_area = (30, 18, 250, 52)
125+
counter_area = (270, 20, 360, 50)
126+
name_list = [self.image_crop(area, copy=True) for area in grid.crop(name_area).buttons]
127+
name_list = TASK_NAME_OCR.ocr(name_list, direct_ocr=True)
128+
task_id_list = [self.task_id_parse(name) for name in name_list]
129+
counter_list = [self.image_crop(area, copy=True) for area in grid.crop(counter_area).buttons]
130+
counter_list = [
131+
TASK_COUNTER_OCR.ocr([image], direct_ocr=True)
132+
for image in counter_list
133+
]
134+
for task_id, counter_result, button in zip(task_id_list, counter_list, grid.buttons):
135+
if task_id is None:
136+
continue
137+
target = DIC_ISLAND_SEASONAL_TASK[task_id]['target']
138+
if target:
139+
target_item = list(target.keys())[0]
140+
current, _, total = counter_result
141+
obtained = TEMPLATE_ISLAND_SEASON_TASK_OBTAINED.match(self.image_crop(button, copy=True))
142+
yield task_id, (target_item, current, total), obtained
143+
144+
def scan_all(self):
145+
"""
146+
Scans all seasonal tasks on the island season page.
147+
148+
Returns:
149+
dict: {
150+
recipe_id: (item_id, current, total)
151+
}
152+
"""
153+
self.wait_until_bar_appear()
154+
logger.hr('Scanning seasonal tasks')
155+
ISLAND_SEASON_TASK_SCROLL.set_top(main=self)
156+
unfinished_tasks = {}
157+
while 1:
158+
for task_id, (target_item, current, total), obtained in self.predict(self.task_grid):
159+
if current < total:
160+
unfinished_tasks[task_id] = (target_item, current, total)
161+
if obtained:
162+
logger.info(f'Detect obtained task, early stop scanning')
163+
return unfinished_tasks
164+
if ISLAND_SEASON_TASK_SCROLL.at_bottom(main=self):
165+
logger.info('Task list reach bottom, stop')
166+
break
167+
else:
168+
ISLAND_SEASON_TASK_SCROLL.next_page(main=self, page=0.5)
169+
del_cached_property(self, 'task_grid')
170+
continue
171+
return unfinished_tasks
172+
173+
def run(self):
174+
"""
175+
Pages:
176+
in: Any page
177+
out: page_island
178+
179+
Returns:
180+
dict: {
181+
recipe_id: (item_id, current, total)
182+
}
183+
"""
184+
self.ui_ensure(page_island_season)
185+
self.island_season_bottom_navbar_ensure(left=3)
186+
result = self.scan_all()
187+
return result

module/island/ui.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from module.base.button import ButtonGrid
2+
from module.base.decorator import cached_property
3+
from module.island.assets import *
4+
from module.ui.navbar import Navbar
5+
from module.ui.scroll import Scroll
6+
from module.ui.ui import UI
7+
8+
9+
ISLAND_SEASON_TASK_SCROLL = Scroll(
10+
ISLAND_SEASON_TASK_SCROLL_AREA.button,
11+
color=(128, 128, 128),
12+
name="ISLAND_SEASON_TASK_SCROLL"
13+
)
14+
15+
16+
class IslandUI(UI):
17+
def ui_additional(self, get_ship=True):
18+
return super().ui_additional(get_ship=False)
19+
20+
@cached_property
21+
def _island_season_bottom_navbar(self):
22+
"""
23+
5 options:
24+
homepage,
25+
pt_reward,
26+
season_task,
27+
season_shop,
28+
season_rank
29+
"""
30+
island_season_bottom_navbar = ButtonGrid(
31+
origin=(54, 677), delta=(246.5, 0),
32+
button_shape=(186, 33), grid_shape=(5, 1),
33+
name='ISLAND_SEASON_BOTTOM_NAVBAR'
34+
)
35+
return Navbar(grids=island_season_bottom_navbar,
36+
active_color=(237, 237, 237),
37+
inactive_color=(65, 78, 96),
38+
active_count=500,
39+
inactive_count=500)
40+
41+
def island_season_bottom_navbar_ensure(self, left=None, right=None):
42+
"""
43+
Args:
44+
left (int):
45+
1 for homepage,
46+
2 for pt_reward,
47+
3 for season_task,
48+
4 for season_shop,
49+
5 for season_rank
50+
right (int):
51+
1 for season_rank,
52+
2 for season_shop,
53+
3 for season_task,
54+
4 for pt_reward,
55+
5 for homepage
56+
"""
57+
if self._island_season_bottom_navbar.set(self, left=left, right=right):
58+
return True
59+
return False

0 commit comments

Comments
 (0)