StarRailCopilot/tasks/assignment/ui.py

214 lines
7.5 KiB
Python
Raw Normal View History

2023-06-19 00:39:41 +00:00
import re
2023-09-26 07:06:43 +00:00
from collections.abc import Iterator
from datetime import timedelta
2023-06-19 00:39:41 +00:00
from functools import cached_property
from module.base.timer import Timer
2023-09-12 10:36:24 +00:00
from module.exception import ScriptError
2023-06-19 00:39:41 +00:00
from module.logger import logger
from module.ocr.ocr import DigitCounter, Duration, Ocr
2023-06-19 00:39:41 +00:00
from module.ui.draggable_list import DraggableList
from tasks.assignment.assets.assets_assignment_ui import *
from tasks.assignment.keywords import *
from tasks.base.ui import UI
class AssignmentOcr(Ocr):
# EN has names in multiple rows
merge_thres_y = 20
merge_thres_x = 20
2023-06-19 00:39:41 +00:00
OCR_REPLACE = {
2023-09-12 10:36:24 +00:00
'cn': [
2023-06-19 00:39:41 +00:00
(KEYWORDS_ASSIGNMENT_ENTRY.Winter_Soldiers.name, '[黑]冬的战士们'),
(KEYWORDS_ASSIGNMENT_ENTRY.Born_to_Obey.name, '[牛]而服从'),
(KEYWORDS_ASSIGNMENT_ENTRY.Root_Out_the_Turpitude.name,
'根除恶[擎薯尊掌鞋]?'),
(KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records.name, '阿[未][夏复]记录'),
2023-09-12 10:36:24 +00:00
(KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master.name, '^师传说'),
(KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity.name, '[赠]养人类'),
2023-09-26 07:06:43 +00:00
(KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force.name,
'[新0]空间站特派[新]'),
],
'en': [
(KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Food_Improvement_Plan.name,
'Food\s*[I]{0}mprovement Plan'),
(KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force.name,
'^(S[np]ace Station Ta[^sk]{0,3})?[F-]orce')
2023-06-19 00:39:41 +00:00
]
}
@cached_property
def ocr_regex(self) -> re.Pattern | None:
rules = AssignmentOcr.OCR_REPLACE.get(self.lang)
if rules is None:
return None
return re.compile('|'.join('(?P<%s>%s)' % pair for pair in rules))
2023-09-17 00:09:00 +00:00
def filter_detected(self, result) -> bool:
# Drop duration rows
res = Duration.timedelta_regex(self.lang).search(result.ocr_text)
2023-09-26 07:06:43 +00:00
if res.group('hours') or res.group('seconds'):
return False
# Locked event assignments
locked_pattern = {
'cn': '解锁$',
'en': 'Locked$',
}[self.lang]
res = re.search(locked_pattern, result.ocr_text)
return not res
2023-09-17 00:09:00 +00:00
2023-06-19 00:39:41 +00:00
def after_process(self, result: str):
result = super().after_process(result)
2023-09-15 17:52:33 +00:00
2023-06-19 00:39:41 +00:00
if self.ocr_regex is None:
return result
matched = self.ocr_regex.fullmatch(result)
if matched is None:
return result
keyword_lang = self.lang
2023-09-26 07:06:43 +00:00
for keyword_class in (
KEYWORDS_ASSIGNMENT_ENTRY, KEYWORDS_ASSIGNMENT_EVENT_ENTRY,
KEYWORDS_ASSIGNMENT_GROUP, KEYWORDS_ASSIGNMENT_EVENT_GROUP,
):
try:
matched = getattr(keyword_class, matched.lastgroup)
break
except AttributeError:
continue
else:
raise ScriptError(f'No keyword found for {matched.lastgroup}')
matched = getattr(matched, keyword_lang)
2023-06-19 00:39:41 +00:00
logger.attr(name=f'{self.name} after_process',
text=f'{result} -> {matched}')
return matched
ASSIGNMENT_GROUP_LIST = DraggableList(
'AssignmentGroupList',
2023-09-26 07:06:43 +00:00
keyword_class=[AssignmentGroup, AssignmentEventGroup],
ocr_class=AssignmentOcr,
search_button=OCR_ASSIGNMENT_GROUP_LIST,
2023-09-26 07:06:43 +00:00
check_row_order=False,
active_color=(240, 240, 240),
drag_direction='right'
2023-06-19 00:39:41 +00:00
)
ASSIGNMENT_ENTRY_LIST = DraggableList(
'AssignmentEntryList',
2023-09-26 07:06:43 +00:00
keyword_class=[AssignmentEntry, AssignmentEventEntry],
2023-06-19 00:39:41 +00:00
ocr_class=AssignmentOcr,
search_button=OCR_ASSIGNMENT_ENTRY_LIST,
check_row_order=False,
2023-06-19 00:39:41 +00:00
active_color=(40, 40, 40)
)
class AssignmentUI(UI):
def goto_group(self, group: AssignmentGroup):
"""
Args:
2023-06-19 17:00:34 +00:00
group (AssignmentGroup):
2023-06-19 00:39:41 +00:00
Examples:
self = AssignmentUI('src')
self.device.screenshot()
self.goto_group(KEYWORDS_ASSIGNMENT_GROUP.Character_Materials)
"""
2023-09-26 07:06:43 +00:00
selected = ASSIGNMENT_GROUP_LIST.get_selected_row(self)
if selected and selected.matched_keyword == group:
return
2023-06-19 00:39:41 +00:00
logger.hr('Assignment group goto', level=3)
2023-09-26 07:06:43 +00:00
self._wait_until_group_loaded()
if ASSIGNMENT_GROUP_LIST.select_row(group, self):
2023-06-19 00:39:41 +00:00
self._wait_until_entry_loaded()
def goto_entry(self, entry: AssignmentEntry):
"""
Args:
2023-06-19 17:00:34 +00:00
entry (AssignmentEntry):
2023-06-19 00:39:41 +00:00
Examples:
self = AssignmentUI('src')
self.device.screenshot()
self.goto_entry(KEYWORDS_ASSIGNMENT_ENTRY.Nameless_Land_Nameless_People)
"""
2023-09-12 10:36:24 +00:00
if entry.group is None:
err_msg = f'{entry} is not in any group, please inform developers if possible'
logger.warning(err_msg)
for group in self._iter_groups():
self.goto_group(group)
if ASSIGNMENT_ENTRY_LIST.select_row(entry, self):
return
raise ScriptError(err_msg)
else:
self.goto_group(entry.group)
ASSIGNMENT_ENTRY_LIST.select_row(entry, self)
2023-06-19 00:39:41 +00:00
2023-09-26 07:06:43 +00:00
def _wait_until_group_loaded(self):
skip_first_screenshot = True
timeout = Timer(2, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
logger.warning('Wait group loaded timeout')
break
if self.image_color_count(OCR_ASSIGNMENT_GROUP_LIST, (40, 40, 40), count=20000) and \
self.image_color_count(OCR_ASSIGNMENT_GROUP_LIST, (240, 240, 240), count=7000):
logger.info('Group loaded')
break
2023-06-19 00:39:41 +00:00
def _wait_until_entry_loaded(self):
skip_first_screenshot = True
timeout = Timer(2, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
logger.warning('Wait entry loaded timeout')
break
# Maybe not reliable
if self.image_color_count(ENTRY_LOADED, (35, 35, 35)):
logger.info('Entry loaded')
break
@property
def _limit_status(self) -> tuple[int, int, int]:
self.device.screenshot()
2023-09-12 10:36:24 +00:00
current, remain, total = DigitCounter(
OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image)
if total and current <= total:
logger.attr('Assignment', f'{current}/{total}')
self.config.stored.Assignment.set(current, total)
else:
logger.warning(f'Invalid assignment limit: {current}/{total}')
self.config.stored.Assignment.set(0, 0)
return current, remain, total
2023-06-19 00:39:41 +00:00
2023-09-26 07:06:43 +00:00
def _get_assignment_time(self) -> timedelta:
return Duration(OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image)
2023-06-19 00:39:41 +00:00
def _iter_groups(self) -> Iterator[AssignmentGroup]:
2023-09-26 07:06:43 +00:00
self._wait_until_group_loaded()
ASSIGNMENT_GROUP_LIST.insight_row(
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, self)
for button in ASSIGNMENT_GROUP_LIST.cur_buttons:
yield button.matched_keyword
2023-06-19 00:39:41 +00:00
def _iter_entries(self) -> Iterator[AssignmentEntry]:
"""
Iterate entries from top to bottom
"""
ASSIGNMENT_ENTRY_LIST.load_rows(main=self)
2023-09-26 07:06:43 +00:00
# Freeze ocr results here
yield from [
button.matched_keyword
for button in ASSIGNMENT_ENTRY_LIST.cur_buttons
]