diff --git a/assets/cn/assignment/dispatch/ASSIGNMENT_START.2.png b/assets/cn/assignment/dispatch/ASSIGNMENT_START.2.png new file mode 100644 index 000000000..a22711f9e Binary files /dev/null and b/assets/cn/assignment/dispatch/ASSIGNMENT_START.2.png differ diff --git a/assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png b/assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png new file mode 100644 index 000000000..2fd108738 Binary files /dev/null and b/assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png differ diff --git a/assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png b/assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png new file mode 100644 index 000000000..96e08dec3 Binary files /dev/null and b/assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png differ diff --git a/assets/en/assignment/dispatch/ASSIGNMENT_START.2.png b/assets/en/assignment/dispatch/ASSIGNMENT_START.2.png new file mode 100644 index 000000000..18dc97cbc Binary files /dev/null and b/assets/en/assignment/dispatch/ASSIGNMENT_START.2.png differ diff --git a/assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png b/assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png new file mode 100644 index 000000000..3429d76ce Binary files /dev/null and b/assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png differ diff --git a/assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png b/assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png new file mode 100644 index 000000000..ab2f0d046 Binary files /dev/null and b/assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png differ diff --git a/assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.2.png b/assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.2.png new file mode 100644 index 000000000..b7a6ddea1 Binary files /dev/null and b/assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.2.png differ diff --git a/dev_tools/keywords/assignment.py b/dev_tools/keywords/assignment.py index 25d53a3e3..218fe79ef 100644 --- a/dev_tools/keywords/assignment.py +++ b/dev_tools/keywords/assignment.py @@ -126,6 +126,7 @@ class GenerateAssignmentEventGroup(GenerateKeyword): def iter_keywords(self) -> Iterable[dict]: yield dict(text_id=self.find_keyword('空间站特派', lang='cn')[0]) + yield dict(text_id=self.find_keyword('关于波提欧的一切…', lang='cn')[0]) class GenerateAssignmentEventEntry(GenerateKeyword): diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 2c6f0adc4..3d5730358 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -907,7 +907,7 @@ "20": "20 Hours" }, "Event": { - "name": "Complete Event Assignments First", + "name": "Complete Event Assignments", "help": "" }, "Assignment": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index a0a857a7a..2a0e29000 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -907,7 +907,7 @@ "20": "20小时" }, "Event": { - "name": "有活动时优先做活动委托", + "name": "有活动时参加活动委托", "help": "" }, "Assignment": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index f3eb18c55..7812e7a19 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -907,7 +907,7 @@ "20": "20小時" }, "Event": { - "name": "有活動時優先做活動委託", + "name": "有活動時參加活動委託", "help": "" }, "Assignment": { diff --git a/tasks/assignment/assets/__init__.py b/tasks/assignment/assets/__init__.py index c2cd3f656..92bc0ebf3 100644 --- a/tasks/assignment/assets/__init__.py +++ b/tasks/assignment/assets/__init__.py @@ -1,12 +1,11 @@ from tasks.assignment.assets.assets_assignment_ui import ( + ALL_ABOUT_BOOTHILL_CHECK, ALL_ABOUT_BOOTHILL_CLICK, CHARACTER_MATERIALS_CHECK, CHARACTER_MATERIALS_CLICK, EXP_MATERIALS_CREDITS_CHECK, EXP_MATERIALS_CREDITS_CLICK, GROUP_SEARCH, - SPACE_STATION_TASK_FORCE_CHECK, SPACE_STATION_TASK_FORCE_CLICK, - SYNTHESIS_MATERIALS_CHECK, SYNTHESIS_MATERIALS_CLICK -) + SYNTHESIS_MATERIALS_CHECK, SYNTHESIS_MATERIALS_CLICK) for group_button_wrapper in ( - SPACE_STATION_TASK_FORCE_CHECK, SPACE_STATION_TASK_FORCE_CLICK, + ALL_ABOUT_BOOTHILL_CHECK, ALL_ABOUT_BOOTHILL_CLICK, CHARACTER_MATERIALS_CHECK, CHARACTER_MATERIALS_CLICK, EXP_MATERIALS_CREDITS_CHECK, EXP_MATERIALS_CREDITS_CLICK, SYNTHESIS_MATERIALS_CHECK, SYNTHESIS_MATERIALS_CLICK, diff --git a/tasks/assignment/assets/assets_assignment_dispatch.py b/tasks/assignment/assets/assets_assignment_dispatch.py index c1965a3f3..6cd2bcf67 100644 --- a/tasks/assignment/assets/assets_assignment_dispatch.py +++ b/tasks/assignment/assets/assets_assignment_dispatch.py @@ -5,30 +5,57 @@ from module.base.button import Button, ButtonWrapper ASSIGNMENT_START = ButtonWrapper( name='ASSIGNMENT_START', - cn=Button( - file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.png', - area=(563, 341, 716, 376), - search=(552, 299, 725, 412), - color=(103, 92, 72), - button=(563, 341, 716, 376), - ), - en=Button( - file='./assets/en/assignment/dispatch/ASSIGNMENT_START.png', - area=(693, 343, 831, 374), - search=(669, 297, 831, 416), - color=(95, 86, 67), - button=(693, 343, 831, 374), - ), + cn=[ + Button( + file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.png', + area=(563, 341, 716, 376), + search=(552, 299, 725, 412), + color=(103, 92, 72), + button=(563, 341, 716, 376), + ), + Button( + file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.2.png', + area=(579, 373, 702, 405), + search=(552, 299, 725, 412), + color=(84, 75, 59), + button=(579, 373, 702, 405), + ), + ], + en=[ + Button( + file='./assets/en/assignment/dispatch/ASSIGNMENT_START.png', + area=(693, 343, 831, 374), + search=(669, 297, 831, 416), + color=(95, 86, 67), + button=(693, 343, 831, 374), + ), + Button( + file='./assets/en/assignment/dispatch/ASSIGNMENT_START.2.png', + area=(676, 376, 787, 404), + search=(669, 297, 831, 416), + color=(80, 71, 56), + button=(676, 376, 787, 404), + ), + ], ) ASSIGNMENT_STARTED_CHECK = ButtonWrapper( name='ASSIGNMENT_STARTED_CHECK', - share=Button( - file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png', - area=(542, 412, 1156, 422), - search=(522, 392, 1176, 442), - color=(232, 230, 226), - button=(542, 412, 1156, 422), - ), + share=[ + Button( + file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png', + area=(542, 412, 1156, 422), + search=(522, 392, 1176, 442), + color=(232, 230, 226), + button=(542, 412, 1156, 422), + ), + Button( + file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.2.png', + area=(542, 412, 1156, 422), + search=(522, 392, 1176, 442), + color=(254, 244, 221), + button=(542, 412, 1156, 422), + ), + ], ) CHARACTER_1 = ButtonWrapper( name='CHARACTER_1', diff --git a/tasks/assignment/assets/assets_assignment_ui.py b/tasks/assignment/assets/assets_assignment_ui.py index da093ac84..1ef0c0780 100644 --- a/tasks/assignment/assets/assets_assignment_ui.py +++ b/tasks/assignment/assets/assets_assignment_ui.py @@ -3,6 +3,40 @@ 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 ``` +ALL_ABOUT_BOOTHILL_CHECK = ButtonWrapper( + name='ALL_ABOUT_BOOTHILL_CHECK', + cn=Button( + file='./assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png', + area=(153, 98, 226, 118), + search=(133, 78, 246, 138), + color=(186, 180, 164), + button=(153, 98, 226, 118), + ), + en=Button( + file='./assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png', + area=(154, 89, 240, 129), + search=(134, 69, 260, 149), + color=(207, 201, 183), + button=(154, 89, 240, 129), + ), +) +ALL_ABOUT_BOOTHILL_CLICK = ButtonWrapper( + name='ALL_ABOUT_BOOTHILL_CLICK', + cn=Button( + file='./assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png', + area=(153, 98, 227, 119), + search=(133, 78, 247, 139), + color=(80, 79, 77), + button=(153, 98, 227, 119), + ), + en=Button( + file='./assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png', + area=(154, 90, 241, 128), + search=(134, 70, 261, 148), + color=(59, 57, 56), + button=(154, 90, 241, 128), + ), +) CHARACTER_MATERIALS_CHECK = ButtonWrapper( name='CHARACTER_MATERIALS_CHECK', cn=Button( diff --git a/tasks/assignment/assignment.py b/tasks/assignment/assignment.py index 758dd5af2..015247cfd 100644 --- a/tasks/assignment/assignment.py +++ b/tasks/assignment/assignment.py @@ -2,7 +2,9 @@ from datetime import datetime from module.logger import logger from tasks.assignment.claim import AssignmentClaim -from tasks.assignment.keywords import (AssignmentEntry, AssignmentEventGroup, KEYWORDS_ASSIGNMENT_GROUP) +from tasks.assignment.keywords import (KEYWORDS_ASSIGNMENT_GROUP, + AssignmentEntry, AssignmentEventEntry, + AssignmentEventGroup) from tasks.assignment.ui import AssignmentStatus from tasks.base.page import page_assignment, page_menu from tasks.daily.keywords import KEYWORDS_DAILY_QUEST @@ -10,7 +12,7 @@ from tasks.daily.synthesize import SynthesizeUI class Assignment(AssignmentClaim, SynthesizeUI): - def run(self, assignments: list[AssignmentEntry] = None, duration: int = None, event_first: bool = None): + def run(self, assignments: list[AssignmentEntry] = None, duration: int = None, join_event: bool = None): self.config.update_battle_pass_quests() self.config.update_daily_quests() @@ -26,25 +28,24 @@ class Assignment(AssignmentClaim, SynthesizeUI): 'There are duplicate assignments in config, check it out') if duration is None: duration = self.config.Assignment_Duration - if event_first is None: - event_first = self.config.Assignment_Event + if join_event is None: + join_event = self.config.Assignment_Event self.dispatched = dict() self.has_new_dispatch = False self.ensure_scroll_top(page_menu) self.ui_ensure(page_assignment) + self._wait_until_group_loaded() event_ongoing = next(( g for g in self._iter_groups() if isinstance(g, AssignmentEventGroup) ), None) - if event_first and event_ongoing is not None: - undispatched = assignments - remain = self._check_all() - remain = self._dispatch_event(remain) - else: - # Iterate in user-specified order, return undispatched ones - undispatched = list(self._check_inlist(assignments, duration)) - remain = self._check_all() + if join_event and event_ongoing is not None: + if self._check_event(): + self._check_event() + # Iterate in user-specified order, return undispatched ones + undispatched = list(self._check_inlist(assignments, duration)) + remain = self._check_all() undispatched = [x for x in undispatched if x not in self.dispatched] # There are unchecked assignments if remain > 0: @@ -89,13 +90,15 @@ class Assignment(AssignmentClaim, SynthesizeUI): logger.hr('Assignment check inlist', level=1) logger.info( f'User specified assignments: {", ".join([x.name for x in assignments])}') - _, remain, _ = self._limit_status + remain = None for assignment in assignments: if assignment in self.dispatched: continue logger.hr('Assignment inlist', level=2) logger.info(f'Check assignment inlist: {assignment}') self.goto_entry(assignment) + if remain is None: + _, remain, _ = self._limit_status status = self._check_assignment_status() if status == AssignmentStatus.CLAIMABLE: self.claim(assignment, duration, should_redispatch=True) @@ -121,10 +124,16 @@ class Assignment(AssignmentClaim, SynthesizeUI): """ logger.hr('Assignment check all', level=1) current, remain, _ = self._limit_status + len_dispatched = len([ + x for x in self.dispatched.keys() + if not isinstance(x, AssignmentEventEntry) + ]) # current = #Claimable + #Dispatched - if current == len(self.dispatched): + if current == len_dispatched: return remain for group in self._iter_groups(): + if isinstance(group, AssignmentEventGroup): + continue self.goto_group(group) insight = False for assignment in self._iter_entries(): @@ -139,14 +148,15 @@ class Assignment(AssignmentClaim, SynthesizeUI): current -= 1 remain += 1 insight = True # Order of entries change after claiming - if current == len(self.dispatched): + if current == len_dispatched: return remain continue if status == AssignmentStatus.DISPATCHED: self.dispatched[assignment] = datetime.now() + \ self._get_assignment_time() + len_dispatched += 1 insight = False # Order of entries does not change here - if current == len(self.dispatched): + if current == len_dispatched: return remain continue break @@ -183,10 +193,9 @@ class Assignment(AssignmentClaim, SynthesizeUI): if remain <= 0: return - def _dispatch_event(self, remain: int): - if remain <= 0: - return remain - logger.hr('Assignment dispatch event', level=1) + def _check_event(self): + logger.hr('Assignment check event', level=1) + claimed = False for group in self._iter_groups(): if not isinstance(group, AssignmentEventGroup): continue @@ -199,12 +208,14 @@ class Assignment(AssignmentClaim, SynthesizeUI): # Order of entries does not change during iteration self.goto_entry(assignment, insight=False) status = self._check_assignment_status() - # Should only be dispatchable or locked after _check_all + if status == AssignmentStatus.LOCKED: + continue + if status == AssignmentStatus.CLAIMABLE: + self.claim(assignment, None, should_redispatch=False) + claimed = True if status == AssignmentStatus.DISPATCHABLE: self.dispatch(assignment, None) - remain -= 1 - if remain <= 0: - return remain - continue - break - return remain + if status == AssignmentStatus.DISPATCHED: + self.dispatched[assignment] = datetime.now() + \ + self._get_assignment_time() + return claimed diff --git a/tasks/assignment/keywords/__init__.py b/tasks/assignment/keywords/__init__.py index eec743922..f37689661 100644 --- a/tasks/assignment/keywords/__init__.py +++ b/tasks/assignment/keywords/__init__.py @@ -30,7 +30,7 @@ KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials.entries = ( KEYWORDS_ASSIGNMENT_ENTRY.Fragments_of_Illusory_Dreams, KEYWORDS_ASSIGNMENT_ENTRY.Scalpel_and_Screwdriver, ) -KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force.entries = ( +KEYWORDS_ASSIGNMENT_EVENT_GROUP.All_About_Boothill.entries = ( KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Activate_Genetic_Samples, KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Reproduce_Experimental_Data, KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Burned_Warehouse, @@ -60,7 +60,7 @@ for group in ( KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits, KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials, - KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force, + KEYWORDS_ASSIGNMENT_EVENT_GROUP.All_About_Boothill, ): for entry in group.entries: assert entry.group is None diff --git a/tasks/assignment/keywords/event_group.py b/tasks/assignment/keywords/event_group.py index f41885793..8f6735559 100644 --- a/tasks/assignment/keywords/event_group.py +++ b/tasks/assignment/keywords/event_group.py @@ -12,3 +12,12 @@ Space_Station_Task_Force = AssignmentEventGroup( jp='ステーション特派', es='Comando de la Estación Espacial', ) +All_About_Boothill = AssignmentEventGroup( + id=2, + name='All_About_Boothill', + cn='关于波提欧的一切…', + cht='關於波提歐的一切……', + en='All About Boothill...', + jp='ブートヒルに関するすべて…', + es='Todo sobre Boothill...', +) diff --git a/tasks/assignment/ui.py b/tasks/assignment/ui.py index efbeb0f67..4742e4685 100644 --- a/tasks/assignment/ui.py +++ b/tasks/assignment/ui.py @@ -8,13 +8,14 @@ from module.base.timer import Timer from module.exception import ScriptError from module.logger import logger from module.ocr.ocr import DigitCounter, Duration, Ocr -from tasks.dungeon.ui import DungeonTabSwitch as Switch from module.ui.draggable_list import DraggableList 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 * from tasks.assignment.keywords import * +from tasks.base.assets.assets_base_page import ASSIGNMENT_CHECK from tasks.base.ui import UI +from tasks.dungeon.ui import DungeonTabSwitch as Switch class AssignmentStatus(Enum): @@ -31,17 +32,19 @@ class AssignmentOcr(Ocr): OCR_REPLACE = { 'cn': [ - (KEYWORDS_ASSIGNMENT_ENTRY.Winter_Soldiers.name, '[黑]冬的战士们'), - (KEYWORDS_ASSIGNMENT_ENTRY.Born_to_Obey.name, '[牛]而服从'), - (KEYWORDS_ASSIGNMENT_ENTRY.Root_Out_the_Turpitude.name, + (KEYWORDS_ASSIGNMENT_ENTRY.Winter_Soldiers, '[黑]冬的战士们'), + (KEYWORDS_ASSIGNMENT_ENTRY.Born_to_Obey, '[牛]而服从'), + (KEYWORDS_ASSIGNMENT_ENTRY.Root_Out_the_Turpitude, '根除恶[擎薯尊掌鞋]?'), - (KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records.name, '阿[未][夏复]记录'), - (KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master.name, '^师传说'), - (KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity.name, '[赠]养人类'), + (KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records, '阿[未][夏复]记录'), + (KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master, '^师传说'), + (KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity, '[赠]养人类'), + (KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Car_Thief, '.*的偷车贼.*'), ], 'en': [ # (KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Food_Improvement_Plan.name, # 'Food\s*[I]{0}mprovement Plan'), + (KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Car_Thief, '.*Car Thief.*'), ] } @@ -50,7 +53,10 @@ class AssignmentOcr(Ocr): 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)) + return re.compile('|'.join( + f'(?P<{kw.name}>{pat})' + for kw, pat in rules + )) def filter_detected(self, result) -> bool: # Drop duration rows @@ -78,17 +84,17 @@ class AssignmentOcr(Ocr): matched = self.ocr_regex.fullmatch(result) if matched is None: return result - keyword_lang = self.lang for keyword_class in ( KEYWORDS_ASSIGNMENT_ENTRY, KEYWORDS_ASSIGNMENT_EVENT_ENTRY, ): - matched = getattr(keyword_class, matched.lastgroup, None) - if matched is not None: + kw = getattr(keyword_class, matched.lastgroup, None) + if kw is not None: + matched = kw break else: raise ScriptError(f'No keyword found for {matched.lastgroup}') - matched = getattr(matched, keyword_lang) + matched = getattr(matched, self.lang) logger.attr(name=f'{self.name} after_process', text=f'{result} -> {matched}') return matched @@ -98,10 +104,15 @@ ASSIGNMENT_GROUP_SWITCH = Switch( 'AssignmentGroupSwitch', is_selector=True ) +# ASSIGNMENT_GROUP_SWITCH.add_state( +# KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force, +# check_button=SPACE_STATION_TASK_FORCE_CHECK, +# click_button=SPACE_STATION_TASK_FORCE_CLICK +# ) ASSIGNMENT_GROUP_SWITCH.add_state( - KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force, - check_button=SPACE_STATION_TASK_FORCE_CHECK, - click_button=SPACE_STATION_TASK_FORCE_CLICK + KEYWORDS_ASSIGNMENT_EVENT_GROUP.All_About_Boothill, + check_button=ALL_ABOUT_BOOTHILL_CHECK, + click_button=ALL_ABOUT_BOOTHILL_CLICK ) ASSIGNMENT_GROUP_SWITCH.add_state( KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, @@ -174,7 +185,7 @@ class AssignmentUI(UI): def _wait_until_group_loaded(self): skip_first_screenshot = True - timeout = Timer(2, count=3).start() + timeout = Timer(3, count=3).start() while 1: if skip_first_screenshot: skip_first_screenshot = False @@ -201,13 +212,17 @@ class AssignmentUI(UI): if timeout.reached(): logger.warning('Wait entry loaded timeout') break - if self.image_color_count(ENTRY_LOADED, (35, 35, 35), count=800): + if self.appear(ASSIGNMENT_CHECK) and \ + self.image_color_count(ENTRY_LOADED, (35, 35, 35), count=800): logger.info('Entry loaded') break @property def _limit_status(self) -> tuple[int, int, int]: self.device.screenshot() + if isinstance(ASSIGNMENT_GROUP_SWITCH.get(self), AssignmentEventGroup): + ASSIGNMENT_GROUP_SWITCH.set( + KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, self) current, remain, total = DigitCounter( OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image) if total and current <= total: