diff --git a/assets/cn/assignment/claim/CLAIM.BUTTON.png b/assets/cn/assignment/claim/CLAIM.BUTTON.png new file mode 100644 index 000000000..4e822ac8f Binary files /dev/null and b/assets/cn/assignment/claim/CLAIM.BUTTON.png differ diff --git a/assets/cn/assignment/claim/CLAIM.png b/assets/cn/assignment/claim/CLAIM.png new file mode 100644 index 000000000..193a9af36 Binary files /dev/null and b/assets/cn/assignment/claim/CLAIM.png differ diff --git a/assets/cn/assignment/claim/CLOSE_REPORT.BUTTON.png b/assets/cn/assignment/claim/CLOSE_REPORT.BUTTON.png new file mode 100644 index 000000000..959bec95e Binary files /dev/null and b/assets/cn/assignment/claim/CLOSE_REPORT.BUTTON.png differ diff --git a/assets/cn/assignment/claim/CLOSE_REPORT.png b/assets/cn/assignment/claim/CLOSE_REPORT.png new file mode 100644 index 000000000..5e9cc57db Binary files /dev/null and b/assets/cn/assignment/claim/CLOSE_REPORT.png differ diff --git a/assets/cn/assignment/claim/REDISPATCH.BUTTON.png b/assets/cn/assignment/claim/REDISPATCH.BUTTON.png new file mode 100644 index 000000000..d8c0b9062 Binary files /dev/null and b/assets/cn/assignment/claim/REDISPATCH.BUTTON.png differ diff --git a/assets/cn/assignment/claim/REDISPATCH.png b/assets/cn/assignment/claim/REDISPATCH.png new file mode 100644 index 000000000..6c8373c57 Binary files /dev/null and b/assets/cn/assignment/claim/REDISPATCH.png differ diff --git a/assets/cn/assignment/dispatch/CHARACTER_LIST.png b/assets/cn/assignment/dispatch/CHARACTER_LIST.png new file mode 100644 index 000000000..50adb0d1c Binary files /dev/null and b/assets/cn/assignment/dispatch/CHARACTER_LIST.png differ diff --git a/assets/cn/assignment/dispatch/CONFIRM_ASSIGNMENT.BUTTON.png b/assets/cn/assignment/dispatch/CONFIRM_ASSIGNMENT.BUTTON.png new file mode 100644 index 000000000..761818dbd Binary files /dev/null and b/assets/cn/assignment/dispatch/CONFIRM_ASSIGNMENT.BUTTON.png differ diff --git a/assets/cn/assignment/dispatch/CONFIRM_ASSIGNMENT.png b/assets/cn/assignment/dispatch/CONFIRM_ASSIGNMENT.png new file mode 100644 index 000000000..f01d59f9c Binary files /dev/null and b/assets/cn/assignment/dispatch/CONFIRM_ASSIGNMENT.png differ diff --git a/assets/cn/assignment/ui/DISPATCHED.png b/assets/cn/assignment/ui/DISPATCHED.png new file mode 100644 index 000000000..3b69f9216 Binary files /dev/null and b/assets/cn/assignment/ui/DISPATCHED.png differ diff --git a/assets/share/assignment/claim/OCR_ASSIGNMENT_REPORT_TIME.png b/assets/share/assignment/claim/OCR_ASSIGNMENT_REPORT_TIME.png new file mode 100644 index 000000000..287dd7e54 Binary files /dev/null and b/assets/share/assignment/claim/OCR_ASSIGNMENT_REPORT_TIME.png differ diff --git a/assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png b/assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png new file mode 100644 index 000000000..bc3588333 Binary files /dev/null and b/assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png differ diff --git a/assets/share/assignment/dispatch/CHARACTER_1.png b/assets/share/assignment/dispatch/CHARACTER_1.png new file mode 100644 index 000000000..ed0fa0469 Binary files /dev/null and b/assets/share/assignment/dispatch/CHARACTER_1.png differ diff --git a/assets/share/assignment/dispatch/CHARACTER_2.png b/assets/share/assignment/dispatch/CHARACTER_2.png new file mode 100644 index 000000000..cb5cbbbe4 Binary files /dev/null and b/assets/share/assignment/dispatch/CHARACTER_2.png differ diff --git a/assets/share/assignment/dispatch/DURATION_12.png b/assets/share/assignment/dispatch/DURATION_12.png new file mode 100644 index 000000000..ed98a0866 Binary files /dev/null and b/assets/share/assignment/dispatch/DURATION_12.png differ diff --git a/assets/share/assignment/dispatch/DURATION_20.png b/assets/share/assignment/dispatch/DURATION_20.png new file mode 100644 index 000000000..c18556dca Binary files /dev/null and b/assets/share/assignment/dispatch/DURATION_20.png differ diff --git a/assets/share/assignment/dispatch/DURATION_4.png b/assets/share/assignment/dispatch/DURATION_4.png new file mode 100644 index 000000000..b4ffb2b2f Binary files /dev/null and b/assets/share/assignment/dispatch/DURATION_4.png differ diff --git a/assets/share/assignment/dispatch/DURATION_8.png b/assets/share/assignment/dispatch/DURATION_8.png new file mode 100644 index 000000000..57f5b8ff8 Binary files /dev/null and b/assets/share/assignment/dispatch/DURATION_8.png differ diff --git a/assets/share/assignment/dispatch/EMPTY_SLOT.SEARCH.png b/assets/share/assignment/dispatch/EMPTY_SLOT.SEARCH.png new file mode 100644 index 000000000..4f96e9044 Binary files /dev/null and b/assets/share/assignment/dispatch/EMPTY_SLOT.SEARCH.png differ diff --git a/assets/share/assignment/dispatch/EMPTY_SLOT.png b/assets/share/assignment/dispatch/EMPTY_SLOT.png new file mode 100644 index 000000000..06cd6cf48 Binary files /dev/null and b/assets/share/assignment/dispatch/EMPTY_SLOT.png differ diff --git a/assets/share/assignment/ui/CHARACTER_MATERIALS.png b/assets/share/assignment/ui/CHARACTER_MATERIALS.png new file mode 100644 index 000000000..a92a59204 Binary files /dev/null and b/assets/share/assignment/ui/CHARACTER_MATERIALS.png differ diff --git a/assets/share/assignment/ui/ENTRY_LOADED.png b/assets/share/assignment/ui/ENTRY_LOADED.png new file mode 100644 index 000000000..cef60fc94 Binary files /dev/null and b/assets/share/assignment/ui/ENTRY_LOADED.png differ diff --git a/assets/share/assignment/ui/EXP_MATERIALS_CREDITS.png b/assets/share/assignment/ui/EXP_MATERIALS_CREDITS.png new file mode 100644 index 000000000..ab4f7717d Binary files /dev/null and b/assets/share/assignment/ui/EXP_MATERIALS_CREDITS.png differ diff --git a/assets/share/assignment/ui/OCR_ASSIGNMENT_LIMIT.png b/assets/share/assignment/ui/OCR_ASSIGNMENT_LIMIT.png new file mode 100644 index 000000000..d1da78934 Binary files /dev/null and b/assets/share/assignment/ui/OCR_ASSIGNMENT_LIMIT.png differ diff --git a/assets/share/assignment/ui/OCR_ASSIGNMENT_LIST.png b/assets/share/assignment/ui/OCR_ASSIGNMENT_LIST.png new file mode 100644 index 000000000..6c5a382ff Binary files /dev/null and b/assets/share/assignment/ui/OCR_ASSIGNMENT_LIST.png differ diff --git a/assets/share/assignment/ui/OCR_ASSIGNMENT_TIME.png b/assets/share/assignment/ui/OCR_ASSIGNMENT_TIME.png new file mode 100644 index 000000000..e7e9e168d Binary files /dev/null and b/assets/share/assignment/ui/OCR_ASSIGNMENT_TIME.png differ diff --git a/assets/share/assignment/ui/SYNTHESIS_MATERIALS.png b/assets/share/assignment/ui/SYNTHESIS_MATERIALS.png new file mode 100644 index 000000000..a07bbdfbd Binary files /dev/null and b/assets/share/assignment/ui/SYNTHESIS_MATERIALS.png differ diff --git a/assets/share/base/page/ASSIGNMENT_CHECK.png b/assets/share/base/page/ASSIGNMENT_CHECK.png new file mode 100644 index 000000000..c9869cf7c Binary files /dev/null and b/assets/share/base/page/ASSIGNMENT_CHECK.png differ diff --git a/assets/share/base/page/MENU_GOTO_ASSIGNMENT.png b/assets/share/base/page/MENU_GOTO_ASSIGNMENT.png new file mode 100644 index 000000000..1dcdd1844 Binary files /dev/null and b/assets/share/base/page/MENU_GOTO_ASSIGNMENT.png differ diff --git a/dev_tools/keyword_extract.py b/dev_tools/keyword_extract.py index 10a0dd701..c46e5a336 100644 --- a/dev_tools/keyword_extract.py +++ b/dev_tools/keyword_extract.py @@ -2,6 +2,7 @@ import os import re import typing as t from functools import cached_property +from collections import namedtuple from module.base.code_generator import CodeGenerator from module.config.utils import deep_get, read_file @@ -12,7 +13,7 @@ UI_LANGUAGES = ['cn', 'cht', 'en', 'jp'] def text_to_variable(text): text = re.sub("'s |s' ", '_', text) - text = re.sub('[ \-—:\']+', '_', text) + text = re.sub('[ \-—:\'/]+', '_', text) text = re.sub(r'[(),#]|', '', text) # text = re.sub(r'[#_]?\d+(_times?)?', '', text) return text @@ -154,6 +155,16 @@ class KeywordExtract: quest_keywords = [self.text_map[lang].find(quest_hash)[1] for quest_hash in quests_hash] self.load_keywords(quest_keywords, lang) + def generate_assignment_keywords(self): + KeywordFromFile = namedtuple('KeywordFromFile', ('file', 'class_name', 'output_file')) + for keyword in ( + KeywordFromFile('ExpeditionGroup.json', 'AssignmentGroup', './tasks/assignment/keywords/group.py'), + KeywordFromFile('ExpeditionData.json', 'AssignmentEntry','./tasks/assignment/keywords/entry.py') + ): + file = os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', keyword.file) + self.load_keywords(deep_get(data, 'Name.Hash') for data in read_file(file).values()) + self.write_keywords(keyword_class=keyword.class_name, output_file=keyword.output_file) + def generate(self): self.load_keywords(['模拟宇宙', '拟造花萼(金)', '拟造花萼(赤)', '凝滞虚影', '侵蚀隧洞', '历战余响', '忘却之庭']) self.write_keywords(keyword_class='DungeonNav', output_file='./tasks/dungeon/keywords/nav.py') @@ -170,6 +181,7 @@ class KeywordExtract: self.write_keywords(keyword_class='DungeonEntrance', output_file='./tasks/dungeon/keywords/dungeon_entrance.py') self.load_keywords(['奖励', '任务']) self.write_keywords(keyword_class='BattlePassTab', output_file='./tasks/battle_pass/keywords/tab.py') + self.generate_assignment_keywords() if __name__ == '__main__': diff --git a/module/ocr/keyword.py b/module/ocr/keyword.py index 12ad779a7..1781ca6c1 100644 --- a/module/ocr/keyword.py +++ b/module/ocr/keyword.py @@ -26,6 +26,9 @@ class Keyword: """ Instance attributes and methods """ + @cached_property + def ch(self) -> str: + return self.cn @cached_property def cn_parsed(self) -> str: diff --git a/module/ocr/ocr.py b/module/ocr/ocr.py index a0964e1a8..7a2a17289 100644 --- a/module/ocr/ocr.py +++ b/module/ocr/ocr.py @@ -1,7 +1,8 @@ +import re import time +from datetime import timedelta import cv2 -import re from ppocronnx.predict_system import BoxedResult import module.config.server as server @@ -257,3 +258,44 @@ class DigitCounter(Ocr): else: logger.warning(f'No digit counter found in {result}') return 0, 0, 0 + + +class Duration(Ocr): + @cached_property + def timedelta_regex(self): + hour_regex = { + 'ch': '小时', + 'en': 'h\s*' + }[self.lang] + minute_regex = { + 'ch': '分钟', + 'en': 'm\s*' + }[self.lang] + second_regex = { + 'ch': '秒', + 'en': 's' + }[self.lang] + ret = rf'\D*((?P\d{{1,2}}){hour_regex})?' + ret += rf'((?P\d{{1,2}}){minute_regex})?' + ret += rf'((?P\d{{1,2}}){second_regex})?' + return re.compile(ret) + + def format_result(self, result: str) -> timedelta: + """ + Do OCR on a duration, such as `2h 13m 30s`, `2h`, `13m 30s`, `9s` + + Returns: + timedelta: + """ + matched = self.timedelta_regex.match(result) + if matched is None: + return timedelta() + hours = self._sanitize_number(matched.group('hours')) + minutes = self._sanitize_number(matched.group('minutes')) + seconds = self._sanitize_number(matched.group('seconds')) + return timedelta(hours=hours, minutes=minutes, seconds=seconds) + + def _sanitize_number(self, number) -> int: + if number is None: + return 0 + return int(number) diff --git a/module/ui/draggable_list.py b/module/ui/draggable_list.py index c45b8d54a..7303d7706 100644 --- a/module/ui/draggable_list.py +++ b/module/ui/draggable_list.py @@ -28,6 +28,7 @@ class DraggableList: keyword_class, ocr_class, search_button: ButtonWrapper, + active_color: tuple[int, int, int] ): """ Args: @@ -42,6 +43,7 @@ class DraggableList: keyword_class = keyword_class[0] self.known_rows = list(keyword_class.instances.values()) self.search_button = search_button + self.active_color = active_color self.row_min = 1 self.row_max = len(self.known_rows) @@ -83,12 +85,14 @@ class DraggableList: self.cur_buttons = self.ocr_class(self.search_button) \ .matched_ocr(main.device.image, self.keyword_class) # Get indexes - indexes = [self.keyword2index(row.matched_keyword) for row in self.cur_buttons] + indexes = [self.keyword2index(row.matched_keyword) + for row in self.cur_buttons] indexes = [index for index in indexes if index] # Check row order if len(indexes) >= 2: if not np.all(np.diff(indexes) > 0): - logger.warning(f'Rows given to {self} are not ascending sorted') + logger.warning( + f'Rows given to {self} are not ascending sorted') if not indexes: logger.warning(f'No valid rows loaded into {self}') return @@ -157,7 +161,8 @@ class DraggableList: elif self.cur_max < row_index: self.drag_page('down', main=main) # Wait for bottoming out - main.wait_until_stable(self.search_button, timer=Timer(0, count=0), timeout=Timer(1.5, count=5)) + main.wait_until_stable(self.search_button, timer=Timer( + 0, count=0), timeout=Timer(1.5, count=5)) skip_first_screenshot = True return True @@ -168,7 +173,7 @@ class DraggableList: return False # Having gold letters - if main.image_color_count(button, color=(190, 175, 124), threshold=221, count=50): + if main.image_color_count(button, color=self.active_color, threshold=221, count=50): return True return False @@ -183,7 +188,8 @@ class DraggableList: Returns: If success """ - result = self.insight_row(row, main=main, skip_first_screenshot=skip_first_screenshot) + result = self.insight_row( + row, main=main, skip_first_screenshot=skip_first_screenshot) if not result: return False diff --git a/module/ui/switch.py b/module/ui/switch.py index f4373cfb4..eddc9472b 100644 --- a/module/ui/switch.py +++ b/module/ui/switch.py @@ -7,7 +7,7 @@ from module.logger import logger class Switch: """ - A wrapper to handle switches in game, switch among states with reties. + A wrapper to handle switches in game, switch among states with retries. Examples: # Definitions diff --git a/tasks/assignment/assets/assets_assignment_claim.py b/tasks/assignment/assets/assets_assignment_claim.py new file mode 100644 index 000000000..947c4704d --- /dev/null +++ b/tasks/assignment/assets/assets_assignment_claim.py @@ -0,0 +1,45 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +CLAIM = ButtonWrapper( + name='CLAIM', + cn=Button( + file='./assets/cn/assignment/claim/CLAIM.png', + area=(1031, 652, 1101, 674), + search=(1011, 632, 1121, 694), + color=(169, 134, 66), + button=(920, 644, 1210, 683), + ), +) +CLOSE_REPORT = ButtonWrapper( + name='CLOSE_REPORT', + cn=Button( + file='./assets/cn/assignment/claim/CLOSE_REPORT.png', + area=(397, 598, 472, 623), + search=(377, 578, 492, 643), + color=(159, 157, 153), + button=(290, 592, 579, 630), + ), +) +OCR_ASSIGNMENT_REPORT_TIME = ButtonWrapper( + name='OCR_ASSIGNMENT_REPORT_TIME', + share=Button( + file='./assets/share/assignment/claim/OCR_ASSIGNMENT_REPORT_TIME.png', + area=(894, 191, 1003, 216), + search=(874, 171, 1023, 236), + color=(62, 63, 63), + button=(894, 191, 1003, 216), + ), +) +REDISPATCH = ButtonWrapper( + name='REDISPATCH', + cn=Button( + file='./assets/cn/assignment/claim/REDISPATCH.png', + area=(784, 598, 901, 622), + search=(764, 578, 921, 642), + color=(158, 157, 155), + button=(700, 592, 987, 629), + ), +) diff --git a/tasks/assignment/assets/assets_assignment_dispatch.py b/tasks/assignment/assets/assets_assignment_dispatch.py new file mode 100644 index 000000000..ebe491149 --- /dev/null +++ b/tasks/assignment/assets/assets_assignment_dispatch.py @@ -0,0 +1,105 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +ASSIGNMENT_STARTED_CHECK = ButtonWrapper( + name='ASSIGNMENT_STARTED_CHECK', + share=Button( + file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png', + area=(1174, 297, 1211, 514), + search=(1154, 277, 1231, 534), + color=(86, 81, 78), + button=(1174, 297, 1211, 514), + ), +) +CHARACTER_1 = ButtonWrapper( + name='CHARACTER_1', + share=Button( + file='./assets/share/assignment/dispatch/CHARACTER_1.png', + area=(116, 212, 206, 312), + search=(96, 192, 226, 332), + color=(149, 134, 123), + button=(116, 212, 206, 312), + ), +) +CHARACTER_2 = ButtonWrapper( + name='CHARACTER_2', + share=Button( + file='./assets/share/assignment/dispatch/CHARACTER_2.png', + area=(228, 211, 318, 311), + search=(208, 191, 338, 331), + color=(184, 161, 172), + button=(228, 211, 318, 311), + ), +) +CHARACTER_LIST = ButtonWrapper( + name='CHARACTER_LIST', + cn=Button( + file='./assets/cn/assignment/dispatch/CHARACTER_LIST.png', + area=(90, 165, 170, 186), + search=(70, 145, 190, 206), + color=(156, 154, 152), + button=(90, 165, 170, 186), + ), +) +CONFIRM_ASSIGNMENT = ButtonWrapper( + name='CONFIRM_ASSIGNMENT', + cn=Button( + file='./assets/cn/assignment/dispatch/CONFIRM_ASSIGNMENT.png', + area=(1024, 653, 1104, 672), + search=(1004, 633, 1124, 692), + color=(154, 154, 153), + button=(920, 645, 1208, 682), + ), +) +DURATION_12 = ButtonWrapper( + name='DURATION_12', + share=Button( + file='./assets/share/assignment/dispatch/DURATION_12.png', + area=(762, 563, 862, 588), + search=(742, 543, 882, 608), + color=(63, 58, 50), + button=(762, 563, 862, 588), + ), +) +DURATION_20 = ButtonWrapper( + name='DURATION_20', + share=Button( + file='./assets/share/assignment/dispatch/DURATION_20.png', + area=(882, 564, 982, 589), + search=(862, 544, 1002, 609), + color=(64, 60, 52), + button=(882, 564, 982, 589), + ), +) +DURATION_4 = ButtonWrapper( + name='DURATION_4', + share=Button( + file='./assets/share/assignment/dispatch/DURATION_4.png', + area=(522, 564, 622, 589), + search=(502, 544, 642, 609), + color=(164, 142, 109), + button=(522, 564, 622, 589), + ), +) +DURATION_8 = ButtonWrapper( + name='DURATION_8', + share=Button( + file='./assets/share/assignment/dispatch/DURATION_8.png', + area=(640, 564, 740, 589), + search=(620, 544, 760, 609), + color=(63, 58, 49), + button=(640, 564, 740, 589), + ), +) +EMPTY_SLOT = ButtonWrapper( + name='EMPTY_SLOT', + share=Button( + file='./assets/share/assignment/dispatch/EMPTY_SLOT.png', + area=(1075, 562, 1110, 597), + search=(1054, 542, 1220, 616), + color=(200, 200, 195), + button=(1075, 562, 1110, 597), + ), +) diff --git a/tasks/assignment/assets/assets_assignment_ui.py b/tasks/assignment/assets/assets_assignment_ui.py new file mode 100644 index 000000000..f21944631 --- /dev/null +++ b/tasks/assignment/assets/assets_assignment_ui.py @@ -0,0 +1,85 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +CHARACTER_MATERIALS = ButtonWrapper( + name='CHARACTER_MATERIALS', + share=Button( + file='./assets/share/assignment/ui/CHARACTER_MATERIALS.png', + area=(123, 81, 307, 134), + search=(103, 61, 327, 154), + color=(234, 233, 229), + button=(123, 81, 307, 134), + ), +) +DISPATCHED = ButtonWrapper( + name='DISPATCHED', + cn=Button( + file='./assets/cn/assignment/ui/DISPATCHED.png', + area=(1032, 652, 1095, 674), + search=(1012, 632, 1115, 694), + color=(99, 93, 85), + button=(1032, 652, 1095, 674), + ), +) +ENTRY_LOADED = ButtonWrapper( + name='ENTRY_LOADED', + share=Button( + file='./assets/share/assignment/ui/ENTRY_LOADED.png', + area=(446, 164, 466, 615), + search=(426, 144, 486, 635), + color=(203, 202, 194), + button=(446, 164, 466, 615), + ), +) +EXP_MATERIALS_CREDITS = ButtonWrapper( + name='EXP_MATERIALS_CREDITS', + share=Button( + file='./assets/share/assignment/ui/EXP_MATERIALS_CREDITS.png', + area=(343, 83, 527, 133), + search=(323, 63, 547, 153), + color=(222, 221, 217), + button=(343, 83, 527, 133), + ), +) +OCR_ASSIGNMENT_LIMIT = ButtonWrapper( + name='OCR_ASSIGNMENT_LIMIT', + share=Button( + file='./assets/share/assignment/ui/OCR_ASSIGNMENT_LIMIT.png', + area=(1095, 86, 1179, 126), + search=(1075, 66, 1199, 146), + color=(62, 61, 60), + button=(1095, 86, 1179, 126), + ), +) +OCR_ASSIGNMENT_LIST = ButtonWrapper( + name='OCR_ASSIGNMENT_LIST', + share=Button( + file='./assets/share/assignment/ui/OCR_ASSIGNMENT_LIST.png', + area=(141, 160, 502, 621), + search=(121, 140, 522, 641), + color=(202, 200, 194), + button=(141, 160, 502, 621), + ), +) +OCR_ASSIGNMENT_TIME = ButtonWrapper( + name='OCR_ASSIGNMENT_TIME', + share=Button( + file='./assets/share/assignment/ui/OCR_ASSIGNMENT_TIME.png', + area=(588, 566, 926, 588), + search=(568, 546, 946, 608), + color=(128, 111, 89), + button=(588, 566, 926, 588), + ), +) +SYNTHESIS_MATERIALS = ButtonWrapper( + name='SYNTHESIS_MATERIALS', + share=Button( + file='./assets/share/assignment/ui/SYNTHESIS_MATERIALS.png', + area=(558, 85, 748, 135), + search=(538, 65, 768, 155), + color=(230, 229, 225), + button=(558, 85, 748, 135), + ), +) diff --git a/tasks/assignment/assignment.py b/tasks/assignment/assignment.py new file mode 100644 index 000000000..9c7000b1b --- /dev/null +++ b/tasks/assignment/assignment.py @@ -0,0 +1,126 @@ +from datetime import datetime + +from module.logger import logger +from module.ocr.ocr import Duration +from tasks.assignment.assets.assets_assignment_claim import CLAIM +from tasks.assignment.assets.assets_assignment_dispatch import EMPTY_SLOT +from tasks.assignment.assets.assets_assignment_ui import (DISPATCHED, + OCR_ASSIGNMENT_TIME) +from tasks.assignment.claim import AssignmentClaim +from tasks.assignment.keywords import * +from tasks.base.page import page_assignment + + +class Assignment(AssignmentClaim): + def run(self, assignments: list[AssignmentEntry] = None, duration: int = None): + if assignments is None: + assignments = [AssignmentEntry.find( + x.strip()) for x in self.config.Assignment_Filter.split('>')] + if duration is None: + duration = self.config.Assignment_Duration + + self.ui_ensure(page_assignment) + # Iterate in user-specified order, return undispatched ones + undispatched = list(self._check_inlist(assignments, duration)) + _, _, total = self._limit_status + # There are unchecked assignments + if total > len(self.dispatched): + self._check_all() + _, remain, _ = self._limit_status + for assignment in undispatched[:remain]: + self.goto_entry(assignment) + self.dispatch(assignment, duration, check_limit=False) + if remain < len(undispatched): + logger.warning( + f'The following assignments can not be dispatched due to limit: {", ".join([x.name for x in undispatched])}') + self._dispatch_remain(duration, remain - len(undispatched)) + + # Scheduler + delay = min(self.dispatched.values()) + logger.info(f'Delay assignment check to {str(delay)}') + self.config.task_delay(target=delay) + + def _check_inlist(self, assignments: list[AssignmentEntry], duration: int): + """ + Dispatch assignments according to user config + + Args: + assignments (list[AssignmentEntry]): user specified assignments + duration (int): user specified duration + """ + if not assignments: + return + logger.hr('Assignment check inlist', level=2) + logger.info( + f'User specified assignments: {", ".join([x.name for x in assignments])}') + for assignment in assignments: + self.goto_entry(assignment) + if self.appear(CLAIM): + self.claim(assignment, duration, should_redispatch=True) + continue + if self.appear(DISPATCHED): + self.dispatched[assignment] = datetime.now() + Duration( + OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image) + continue + if self.appear(EMPTY_SLOT): + dispatched = self.dispatch(assignment, duration) + if not dispatched: + yield assignment + + def _check_all(self): + """ + States of assignments from top to bottom are in following order: + 1. Claimable + 2. Dispatched + 3. Dispatchable + Break when a dispatchable assignment is encountered + """ + logger.hr('Assignment check all', level=2) + for group in self._iter_groups(): + self.goto_group(group) + entries = self._iter_entries() + for _ in range(len(group.entries)): + assignment = next(entries) + if assignment in self.dispatched: + continue + self.goto_entry(assignment) + if self.appear(CLAIM): + self.claim(assignment, None, should_redispatch=False) + continue + if self.appear(DISPATCHED): + self.dispatched[assignment] = datetime.now() + Duration( + OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image) + continue + if self.appear(EMPTY_SLOT): + break + + def _dispatch_remain(self, duration: int, remain: int): + """ + Dispatch assignments according to preset priority + + Args: + duration (int): user specified duration + remain (int): + The number of remaining assignments after + processing the ones specified by user + """ + if remain <= 0: + return + logger.hr('Assignment dispatch remain', level=2) + logger.warning(f'{remain} remain') + logger.info( + 'Dispatch remaining assignments according to preset priority') + group_priority = ( + KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits, + KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, + KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials + ) + for group in group_priority: + for assignment in group.entries: + if assignment in self.dispatched: + continue + self.goto_entry(assignment) + self.dispatch(assignment, duration, check_limit=False) + remain -= 1 + if remain <= 0: + return diff --git a/tasks/assignment/claim.py b/tasks/assignment/claim.py new file mode 100644 index 000000000..baeb26264 --- /dev/null +++ b/tasks/assignment/claim.py @@ -0,0 +1,68 @@ +from datetime import datetime, timedelta + +from module.base.timer import Timer +from module.ocr.ocr import Duration +from tasks.assignment.assets.assets_assignment_claim import * +from tasks.assignment.assets.assets_assignment_dispatch import EMPTY_SLOT +from tasks.assignment.assets.assets_assignment_ui import DISPATCHED +from tasks.assignment.dispatch import AssignmentDispatch +from tasks.assignment.keywords import AssignmentEntry + + +class AssignmentClaim(AssignmentDispatch): + def claim(self, assignment: AssignmentEntry, duration_expected: int, should_redispatch: bool): + """ + Args: + assignment (AssignmentEntry): + duration_expected (int): user specified duration + should_redispatch (bool): + + Pages: + in: CLAIM + out: DISPATCHED(succeed) or EMPTY_SLOT(fail) + """ + redispatched = False + skip_first_screenshot = True + counter = Timer(1, count=4).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + # End + if self.appear(EMPTY_SLOT) or self.appear(DISPATCHED): + if counter.reached(): + break + continue + # Claim reward + if self.appear(CLAIM, interval=2): + self.device.click(CLAIM) + continue + if self.appear(REDISPATCH, interval=2): + redispatched = should_redispatch and self._is_duration_expected( + duration_expected) + if redispatched: + self._confirm_assignment(REDISPATCH) + self.dispatched[assignment] = datetime.now( + ) + timedelta(hours=duration_expected) + else: + self.device.click(CLOSE_REPORT) + continue + # Re-select duration and dispatch + if should_redispatch and not redispatched: + self.dispatch(assignment, duration_expected, check_limit=False) + + def _is_duration_expected(self, duration: int) -> bool: + """ + Check whether duration in assignment report page + is the same as user specified + + Args: + duration (int): user specified duration + + Returns: + bool: If same. + """ + duration_reported: timedelta = Duration( + OCR_ASSIGNMENT_REPORT_TIME).ocr_single_line(self.device.image) + return duration_reported.total_seconds() == duration*3600 diff --git a/tasks/assignment/dispatch.py b/tasks/assignment/dispatch.py new file mode 100644 index 000000000..99ba0cf0a --- /dev/null +++ b/tasks/assignment/dispatch.py @@ -0,0 +1,104 @@ +from datetime import datetime, timedelta + +from module.base.timer import Timer +from module.logger import logger +from tasks.assignment.assets.assets_assignment_dispatch import * +from tasks.assignment.assets.assets_assignment_ui import DISPATCHED +from tasks.assignment.keywords import * +from tasks.assignment.ui import AssignmentSwitch, AssignmentUI + +ASSIGNMENT_DURATION_SWITCH = AssignmentSwitch( + 'AssignmentDurationSwitch', + (160, 130, 100) +) +ASSIGNMENT_DURATION_SWITCH.add_state('4', DURATION_4) +ASSIGNMENT_DURATION_SWITCH.add_state('8', DURATION_8) +ASSIGNMENT_DURATION_SWITCH.add_state('12', DURATION_12) +ASSIGNMENT_DURATION_SWITCH.add_state('20', DURATION_20) + + +class AssignmentDispatch(AssignmentUI): + dispatched: dict[AssignmentEntry, datetime] = dict() + + def dispatch(self, assignment: AssignmentEntry, duration: int, check_limit: bool = True) -> bool: + """ + Args: + assignment (AssignmentEntry): + duration (int): user specified duration + check_limit (bool): + + Pages: + in: EMPTY_SLOT + out: DISPATCHED(succeed) or EMPTY_SLOT(fail) + """ + if check_limit and self._limit_status[1] == 0: + return False + self._select_characters() + self._select_duration(duration) + self._confirm_assignment(CONFIRM_ASSIGNMENT) + self.dispatched[assignment] = datetime.now() + \ + timedelta(hours=duration) + return True + + def _select_characters(self): + """ + Pages: + in: EMPTY_SLOT + out: CHARACTER_LIST + """ + skip_first_screenshot = True + click_timer = Timer(1, count=3).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + # End + if not self.appear(EMPTY_SLOT): + logger.info('Assignment slots are all filled') + break + # Ensure character list + if not self.appear(CHARACTER_LIST): + if click_timer.reached_and_reset(): + self.device.click(EMPTY_SLOT) + continue + # Select + if click_timer.reached_and_reset(): + self.device.click(CHARACTER_1) + self.device.click(CHARACTER_2) + + def _select_duration(self, duration: int): + if duration not in {4, 8, 12, 20}: + logger.warning( + f'Duration {duration} is out of scope, reset it to 20') + duration = 20 + ASSIGNMENT_DURATION_SWITCH.set(str(duration), self) + + def _confirm_assignment(self, dispatch_button: ButtonWrapper) -> bool: + """ + Args: + dispatch_button (ButtonWrapper): + Button to be clicked, CONFIRM_ASSIGNMENT or REDISPATCH + + Pages: + in: CONFIRM_ASSIGNMENT or REDISPATCH + out: DISPATCHED + """ + skip_first_screenshot = True + counter = Timer(1, count=3).start() + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + # End + if self.appear(DISPATCHED) and self.appear(ASSIGNMENT_STARTED_CHECK): + if counter.reached(): + logger.info(f'Assignment started') + break + continue + # Click + if self.appear(dispatch_button, interval=2): + self.device.click(dispatch_button) + counter.reset() + continue diff --git a/tasks/assignment/keywords/__init__.py b/tasks/assignment/keywords/__init__.py new file mode 100644 index 000000000..3ecbdeebf --- /dev/null +++ b/tasks/assignment/keywords/__init__.py @@ -0,0 +1,30 @@ +import tasks.assignment.keywords.entry as KEYWORDS_ASSIGNMENT_ENTRY +import tasks.assignment.keywords.group as KEYWORDS_ASSIGNMENT_GROUP +from tasks.assignment.keywords.classes import AssignmentGroup, AssignmentEntry + +KEYWORDS_ASSIGNMENT_GROUP.Character_Materials.entries = ( + KEYWORDS_ASSIGNMENT_ENTRY.Nine_Billion_Names, + KEYWORDS_ASSIGNMENT_ENTRY.Destruction_of_the_Destroyer, + KEYWORDS_ASSIGNMENT_ENTRY.Winter_Soldiers, + KEYWORDS_ASSIGNMENT_ENTRY.Born_to_Obey, + KEYWORDS_ASSIGNMENT_ENTRY.Root_Out_the_Turpitude, + KEYWORDS_ASSIGNMENT_ENTRY.Fire_Lord_Inflames_Blades_of_War, +) +KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits.entries = ( + KEYWORDS_ASSIGNMENT_ENTRY.Nameless_Land_Nameless_People, + KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records, + KEYWORDS_ASSIGNMENT_ENTRY.The_Invisible_Hand, +) +KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials.entries = ( + KEYWORDS_ASSIGNMENT_ENTRY.Abandoned_and_Insulted, + KEYWORDS_ASSIGNMENT_ENTRY.Spring_of_Life, + KEYWORDS_ASSIGNMENT_ENTRY.The_Land_of_Gold, + KEYWORDS_ASSIGNMENT_ENTRY.The_Blossom_in_the_Storm, +) +for group in ( + KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, + KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits, + KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials, +): + for entry in group.entries: + entry.group = group \ No newline at end of file diff --git a/tasks/assignment/keywords/classes.py b/tasks/assignment/keywords/classes.py new file mode 100644 index 000000000..363021e44 --- /dev/null +++ b/tasks/assignment/keywords/classes.py @@ -0,0 +1,20 @@ +from __future__ import annotations +from dataclasses import dataclass +from typing import ClassVar + +from module.ocr.keyword import Keyword + + +@dataclass +class AssignmentGroup(Keyword): + instances: ClassVar = {} + entries: tuple[AssignmentEntry] = () + + +@dataclass +class AssignmentEntry(Keyword): + instances: ClassVar = {} + group: AssignmentGroup = None + def __hash__(self) -> int: + return super().__hash__() + diff --git a/tasks/assignment/keywords/entry.py b/tasks/assignment/keywords/entry.py new file mode 100644 index 000000000..70f6b06b7 --- /dev/null +++ b/tasks/assignment/keywords/entry.py @@ -0,0 +1,109 @@ +from .classes import AssignmentEntry + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Nine_Billion_Names = AssignmentEntry( + id=1, + name='Nine_Billion_Names', + cn='九十亿个名字', + cht='九十億個名字', + en='Nine Billion Names', + jp='九十億の御名', +) +Destruction_of_the_Destroyer = AssignmentEntry( + id=2, + name='Destruction_of_the_Destroyer', + cn='毁灭者的覆灭', + cht='毀滅者的覆滅', + en='Destruction of the Destroyer', + jp='壊滅者の覆没', +) +Winter_Soldiers = AssignmentEntry( + id=3, + name='Winter_Soldiers', + cn='寒冬的战士们', + cht='寒冬的戰士們', + en='Winter Soldiers', + jp='寒冬の戦士たち', +) +Born_to_Obey = AssignmentEntry( + id=4, + name='Born_to_Obey', + cn='生而服从', + cht='生而服從', + en='Born to Obey', + jp='生まれながらに服従する', +) +Root_Out_the_Turpitude = AssignmentEntry( + id=5, + name='Root_Out_the_Turpitude', + cn='根除恶孽', + cht='根除惡孽', + en='Root Out the Turpitude', + jp='悪孽を根絶やしに', +) +Fire_Lord_Inflames_Blades_of_War = AssignmentEntry( + id=6, + name='Fire_Lord_Inflames_Blades_of_War', + cn='火帝动炉销剑戟', + cht='火帝動爐銷劍戟', + en='Fire Lord Inflames Blades of War', + jp='剣戟を焼却する火帝炉', +) +Nameless_Land_Nameless_People = AssignmentEntry( + id=7, + name='Nameless_Land_Nameless_People', + cn='无名之地,无名之人', + cht='無名之地,無名之人', + en='Nameless Land, Nameless People', + jp='無名の地、無名の人', +) +Akashic_Records = AssignmentEntry( + id=8, + name='Akashic_Records', + cn='阿卡夏记录', + cht='阿卡夏紀錄', + en='Akashic Records', + jp='アーカーシャの記録', +) +The_Invisible_Hand = AssignmentEntry( + id=9, + name='The_Invisible_Hand', + cn='看不见的手', + cht='看不見的手', + en='The Invisible Hand', + jp='見えざる手', +) +Abandoned_and_Insulted = AssignmentEntry( + id=10, + name='Abandoned_and_Insulted', + cn='被废弃与损害的', + cht='被廢棄與損害的', + en='Abandoned and Insulted', + jp='捨てられしものと傷つけられしもの', +) +Spring_of_Life = AssignmentEntry( + id=11, + name='Spring_of_Life', + cn='生命之泉', + cht='生命之泉', + en='Spring of Life', + jp='生命の泉', +) +The_Land_of_Gold = AssignmentEntry( + id=12, + name='The_Land_of_Gold', + cn='黄金大地', + cht='黃金大地', + en='The Land of Gold', + jp='黄金の大地', +) +The_Blossom_in_the_Storm = AssignmentEntry( + id=13, + name='The_Blossom_in_the_Storm', + cn='风暴中怒放的花', + cht='風暴中怒放的花', + en='The Blossom in the Storm', + jp='嵐の中で咲き誇る花', +) diff --git a/tasks/assignment/keywords/group.py b/tasks/assignment/keywords/group.py new file mode 100644 index 000000000..9f12793ea --- /dev/null +++ b/tasks/assignment/keywords/group.py @@ -0,0 +1,29 @@ +from .classes import AssignmentGroup + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.keyword_extract ``` + +Character_Materials = AssignmentGroup( + id=1, + name='Character_Materials', + cn='专属材料', + cht='專屬素材', + en='Character Materials', + jp='専用素材', +) +EXP_Materials_Credits = AssignmentGroup( + id=2, + name='EXP_Materials_Credits', + cn='经验材料/信用点', + cht='經驗素材/信用點', + en='EXP Materials/Credits', + jp='経験値素材/信用ポイント', +) +Synthesis_Materials = AssignmentGroup( + id=3, + name='Synthesis_Materials', + cn='合成材料', + cht='合成材料', + en='Synthesis Materials', + jp='合成材料', +) diff --git a/tasks/assignment/ui.py b/tasks/assignment/ui.py new file mode 100644 index 000000000..7eec695fc --- /dev/null +++ b/tasks/assignment/ui.py @@ -0,0 +1,162 @@ +import re +from functools import cached_property +from typing import Iterator + +from module.base.base import ModuleBase +from module.base.timer import Timer +from module.logger import logger +from module.ocr.ocr import DigitCounter, Ocr +from module.ui.draggable_list import DraggableList +from module.ui.switch import Switch +from tasks.assignment.assets.assets_assignment_ui import * +from tasks.assignment.keywords import * +from tasks.base.page import page_assignment +from tasks.base.ui import UI + + +class AssignmentSwitch(Switch): + def __init__(self, name, active_color: tuple[int, int, int], is_selector=True): + super().__init__(name, is_selector) + self.active_color = active_color + + def get(self, main: ModuleBase): + """ + Use image_color_count instead to determine whether the button is selected/active + + Args: + main (ModuleBase): + + Returns: + str: state name or 'unknown'. + """ + for data in self.state_list: + if main.image_color_count(data['check_button'], self.active_color): + return data['state'] + + return 'unknown' + + +class AssignmentOcr(Ocr): + OCR_REPLACE = { + 'ch': [ + (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, '阿[未][夏复]记录'), + ] + } + + @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)) + + def after_process(self, result: str): + result = super().after_process(result) + if self.ocr_regex is None: + return result + matched = self.ocr_regex.fullmatch(result) + if matched is None: + return result + matched = getattr(KEYWORDS_ASSIGNMENT_ENTRY, matched.lastgroup) + matched = getattr(matched, self.lang) + logger.attr(name=f'{self.name} after_process', + text=f'{result} -> {matched}') + return matched + + +ASSIGNMENT_TOP_SWITCH = AssignmentSwitch( + 'AssignmentTopSwitch', + (240, 240, 240) +) +ASSIGNMENT_TOP_SWITCH.add_state( + KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, + check_button=CHARACTER_MATERIALS +) +ASSIGNMENT_TOP_SWITCH.add_state( + KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits, + check_button=EXP_MATERIALS_CREDITS +) +ASSIGNMENT_TOP_SWITCH.add_state( + KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials, + check_button=SYNTHESIS_MATERIALS +) + +ASSIGNMENT_ENTRY_LIST = DraggableList( + 'AssignmentEntryList', + keyword_class=AssignmentEntry, + ocr_class=AssignmentOcr, + search_button=OCR_ASSIGNMENT_LIST, + active_color=(40, 40, 40) +) + + +class AssignmentUI(UI): + def goto_group(self, group: AssignmentGroup): + """ + Args: + group: AssignmentGroup + + Examples: + self = AssignmentUI('src') + self.device.screenshot() + self.goto_group(KEYWORDS_ASSIGNMENT_GROUP.Character_Materials) + """ + self.ui_ensure(page_assignment) + logger.hr('Assignment group goto', level=3) + if ASSIGNMENT_TOP_SWITCH.set(group, main=self): + self._wait_until_entry_loaded() + + def goto_entry(self, entry: AssignmentEntry): + """ + Args: + entry: AssignmentEntry + + Examples: + self = AssignmentUI('src') + self.device.screenshot() + self.goto_entry(KEYWORDS_ASSIGNMENT_ENTRY.Nameless_Land_Nameless_People) + """ + self.goto_group(entry.group) + ASSIGNMENT_ENTRY_LIST.select_row(entry, self) + + 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() + return DigitCounter(OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image) + + def _iter_groups(self) -> Iterator[AssignmentGroup]: + for state in ASSIGNMENT_TOP_SWITCH.state_list: + yield state['state'] + + def _iter_entries(self) -> Iterator[AssignmentEntry]: + """ + Iterate entries from top to bottom + """ + while 1: + ASSIGNMENT_ENTRY_LIST.load_rows(self) + for button in ASSIGNMENT_ENTRY_LIST.cur_buttons: + yield button.matched_keyword + ASSIGNMENT_ENTRY_LIST.drag_page('down', self) + self.wait_until_stable(ASSIGNMENT_ENTRY_LIST.search_button, timer=Timer( + 0, count=0), timeout=Timer(1.5, count=5)) diff --git a/tasks/base/assets/assets_base_page.py b/tasks/base/assets/assets_base_page.py index 9e199cba5..d8e1404e1 100644 --- a/tasks/base/assets/assets_base_page.py +++ b/tasks/base/assets/assets_base_page.py @@ -3,6 +3,16 @@ from module.base.button import Button, ButtonWrapper # This file was auto-generated, do not modify it manually. To generate: # ``` python -m dev_tools.button_extract ``` +ASSIGNMENT_CHECK = ButtonWrapper( + name='ASSIGNMENT_CHECK', + share=Button( + file='./assets/share/base/page/ASSIGNMENT_CHECK.png', + area=(45, 21, 70, 53), + search=(25, 1, 90, 73), + color=(162, 145, 112), + button=(45, 21, 70, 53), + ), +) BATTLE_PASS_CHECK = ButtonWrapper( name='BATTLE_PASS_CHECK', share=Button( @@ -213,6 +223,16 @@ MENU_CHECK = ButtonWrapper( button=(1222, 638, 1252, 669), ), ) +MENU_GOTO_ASSIGNMENT = ButtonWrapper( + name='MENU_GOTO_ASSIGNMENT', + share=Button( + file='./assets/share/base/page/MENU_GOTO_ASSIGNMENT.png', + area=(1090, 269, 1153, 328), + search=(1070, 249, 1173, 348), + color=(71, 71, 74), + button=(1090, 269, 1153, 328), + ), +) MENU_GOTO_CAMERA = ButtonWrapper( name='MENU_GOTO_CAMERA', share=Button( diff --git a/tasks/base/page.py b/tasks/base/page.py index 9e329636b..d4a4678e0 100644 --- a/tasks/base/page.py +++ b/tasks/base/page.py @@ -135,3 +135,8 @@ page_menu.link(MENU_GOTO_CAMERA, destination=page_camera) page_synthesize = Page(SYNTHESIZE_CHECK) page_synthesize.link(CLOSE, destination=page_menu) page_menu.link(MENU_GOTO_SYNTHESIZE, destination=page_synthesize) + +# Assignment +page_assignment = Page(ASSIGNMENT_CHECK) +page_assignment.link(CLOSE, destination=page_main) +page_menu.link(MENU_GOTO_ASSIGNMENT, destination=page_assignment) diff --git a/tasks/dungeon/ui.py b/tasks/dungeon/ui.py index ae4b830f2..cc60d3427 100644 --- a/tasks/dungeon/ui.py +++ b/tasks/dungeon/ui.py @@ -92,10 +92,10 @@ class DraggableDungeonList(DraggableList): DUNGEON_NAV_LIST = DraggableList( - 'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV) + 'DungeonNavList', keyword_class=DungeonNav, ocr_class=OcrDungeonNav, search_button=OCR_DUNGEON_NAV, active_color=(190, 175, 124)) DUNGEON_LIST = DraggableDungeonList( 'DungeonList', keyword_class=[DungeonList, DungeonEntrance], - ocr_class=OcrDungeonList, search_button=OCR_DUNGEON_LIST) + ocr_class=OcrDungeonList, search_button=OCR_DUNGEON_LIST, active_color=(190, 175, 124)) class DungeonUI(UI):