Add: Support for event assignments
BIN
assets/cn/assignment/dispatch/ASSIGNMENT_START.SEARCH.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
assets/cn/assignment/dispatch/CHARACTER_SUPPORT_LIST.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
assets/en/assignment/dispatch/ASSIGNMENT_START.SEARCH.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/en/assignment/dispatch/CHARACTER_SUPPORT_LIST.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
assets/share/assignment/claim/REPORT.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
assets/share/assignment/dispatch/CHARACTER_SUPPORT.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
assets/share/assignment/dispatch/CHARACTER_SUPPORT_SELECTED.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 34 KiB |
BIN
assets/share/assignment/dispatch/EMPTY_SLOT_SUPPORT.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 10 KiB |
BIN
assets/share/assignment/ui/LOCKED.png
Normal file
After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 8.2 KiB |
@ -1,7 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
import typing as t
|
||||
from collections import namedtuple
|
||||
from functools import cached_property
|
||||
|
||||
from module.base.code_generator import CodeGenerator
|
||||
@ -254,14 +253,19 @@ class KeywordExtract:
|
||||
self.clear_keywords()
|
||||
|
||||
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')
|
||||
self.load_keywords(['空间站特派'])
|
||||
self.write_keywords(
|
||||
keyword_class='AssignmentEventGroup',
|
||||
output_file='./tasks/assignment/keywords/event_group.py'
|
||||
)
|
||||
for file_name, class_name, output_file in (
|
||||
('ExpeditionGroup.json', 'AssignmentGroup', './tasks/assignment/keywords/group.py'),
|
||||
('ExpeditionData.json', 'AssignmentEntry', './tasks/assignment/keywords/entry.py'),
|
||||
('ActivityExpedition.json', 'AssignmentEventEntry', './tasks/assignment/keywords/event_entry.py'),
|
||||
):
|
||||
file = os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', keyword.file)
|
||||
file = os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', file_name)
|
||||
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)
|
||||
self.write_keywords(keyword_class=class_name, output_file=output_file)
|
||||
|
||||
def generate_map_planes(self):
|
||||
planes = {
|
||||
|
@ -375,17 +375,17 @@ class Duration(Ocr):
|
||||
def timedelta_regex(cls, lang):
|
||||
regex_str = {
|
||||
'cn': r'^(?P<prefix>.*?)'
|
||||
r'((?P<days>\d{1,2})天)?'
|
||||
r'((?P<hours>\d{1,2})小时)?'
|
||||
r'((?P<minutes>\d{1,2})分钟)?'
|
||||
r'((?P<seconds>\d{1,2})秒)?'
|
||||
r'$',
|
||||
r'((?P<days>\d{1,2})\s*天\s*)?'
|
||||
r'((?P<hours>\d{1,2})\s*小时\s*)?'
|
||||
r'((?P<minutes>\d{1,2})\s*分钟\s*)?'
|
||||
r'((?P<seconds>\d{1,2})\s*秒)?'
|
||||
r'(?P<suffix>[^天时钟秒]*?)$',
|
||||
'en': r'^(?P<prefix>.*?)'
|
||||
r'((?P<days>\d{1,2})\s*d\s*)?'
|
||||
r'((?P<hours>\d{1,2})\s*h\s*)?'
|
||||
r'((?P<minutes>\d{1,2})\s*m\s*)?'
|
||||
r'((?P<seconds>\d{1,2})\s*s)?'
|
||||
r'$'
|
||||
r'(?P<suffix>[^dhms]*?)$'
|
||||
}[lang]
|
||||
return re.compile(regex_str)
|
||||
|
||||
|
@ -42,9 +42,11 @@ class DraggableList:
|
||||
self.name = name
|
||||
self.keyword_class = keyword_class
|
||||
self.ocr_class = ocr_class
|
||||
if isinstance(keyword_class, list):
|
||||
keyword_class = keyword_class[0]
|
||||
self.known_rows = list(keyword_class.instances.values())
|
||||
if not isinstance(keyword_class, list):
|
||||
keyword_class = [keyword_class]
|
||||
self.known_rows = [
|
||||
kw for kc in keyword_class for kw in kc.instances.values()
|
||||
]
|
||||
self.search_button = search_button
|
||||
self.check_row_order = check_row_order
|
||||
self.active_color = active_color
|
||||
|
@ -43,3 +43,13 @@ REDISPATCH = ButtonWrapper(
|
||||
button=(779, 592, 905, 629),
|
||||
),
|
||||
)
|
||||
REPORT = ButtonWrapper(
|
||||
name='REPORT',
|
||||
share=Button(
|
||||
file='./assets/share/assignment/claim/REPORT.png',
|
||||
area=(0, 154, 266, 542),
|
||||
search=(0, 134, 286, 562),
|
||||
color=(33, 34, 37),
|
||||
button=(0, 154, 266, 542),
|
||||
),
|
||||
)
|
||||
|
@ -8,14 +8,14 @@ ASSIGNMENT_START = ButtonWrapper(
|
||||
cn=Button(
|
||||
file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.png',
|
||||
area=(581, 321, 699, 349),
|
||||
search=(561, 301, 719, 369),
|
||||
search=(573, 299, 707, 412),
|
||||
color=(93, 84, 66),
|
||||
button=(581, 321, 699, 349),
|
||||
),
|
||||
en=Button(
|
||||
file='./assets/en/assignment/dispatch/ASSIGNMENT_START.png',
|
||||
area=(679, 323, 784, 347),
|
||||
search=(659, 303, 804, 367),
|
||||
search=(669, 297, 794, 416),
|
||||
color=(93, 83, 65),
|
||||
button=(679, 323, 784, 347),
|
||||
),
|
||||
@ -87,6 +87,43 @@ CHARACTER_LIST = ButtonWrapper(
|
||||
button=(91, 163, 136, 180),
|
||||
),
|
||||
)
|
||||
CHARACTER_SUPPORT = ButtonWrapper(
|
||||
name='CHARACTER_SUPPORT',
|
||||
share=Button(
|
||||
file='./assets/share/assignment/dispatch/CHARACTER_SUPPORT.png',
|
||||
area=(103, 212, 435, 302),
|
||||
search=(83, 192, 455, 322),
|
||||
color=(62, 60, 63),
|
||||
button=(103, 212, 435, 302),
|
||||
),
|
||||
)
|
||||
CHARACTER_SUPPORT_LIST = ButtonWrapper(
|
||||
name='CHARACTER_SUPPORT_LIST',
|
||||
cn=Button(
|
||||
file='./assets/cn/assignment/dispatch/CHARACTER_SUPPORT_LIST.png',
|
||||
area=(91, 166, 171, 186),
|
||||
search=(71, 146, 191, 206),
|
||||
color=(147, 146, 143),
|
||||
button=(91, 166, 171, 186),
|
||||
),
|
||||
en=Button(
|
||||
file='./assets/en/assignment/dispatch/CHARACTER_SUPPORT_LIST.png',
|
||||
area=(90, 167, 267, 189),
|
||||
search=(70, 147, 287, 209),
|
||||
color=(169, 168, 165),
|
||||
button=(90, 167, 267, 189),
|
||||
),
|
||||
)
|
||||
CHARACTER_SUPPORT_SELECTED = ButtonWrapper(
|
||||
name='CHARACTER_SUPPORT_SELECTED',
|
||||
share=Button(
|
||||
file='./assets/share/assignment/dispatch/CHARACTER_SUPPORT_SELECTED.png',
|
||||
area=(190, 270, 266, 295),
|
||||
search=(170, 250, 286, 315),
|
||||
color=(39, 39, 39),
|
||||
button=(190, 270, 266, 295),
|
||||
),
|
||||
)
|
||||
CONFIRM_ASSIGNMENT = ButtonWrapper(
|
||||
name='CONFIRM_ASSIGNMENT',
|
||||
cn=Button(
|
||||
@ -149,8 +186,18 @@ EMPTY_SLOT = ButtonWrapper(
|
||||
share=Button(
|
||||
file='./assets/share/assignment/dispatch/EMPTY_SLOT.png',
|
||||
area=(1075, 562, 1110, 597),
|
||||
search=(1054, 542, 1220, 616),
|
||||
search=(873, 543, 1099, 609),
|
||||
color=(200, 200, 195),
|
||||
button=(1075, 562, 1110, 597),
|
||||
),
|
||||
)
|
||||
EMPTY_SLOT_SUPPORT = ButtonWrapper(
|
||||
name='EMPTY_SLOT_SUPPORT',
|
||||
share=Button(
|
||||
file='./assets/share/assignment/dispatch/EMPTY_SLOT_SUPPORT.png',
|
||||
area=(1152, 561, 1187, 592),
|
||||
search=(1132, 541, 1207, 612),
|
||||
color=(203, 202, 198),
|
||||
button=(1152, 561, 1187, 592),
|
||||
),
|
||||
)
|
||||
|
@ -3,16 +3,6 @@ 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=(146, 91, 255, 124),
|
||||
search=(126, 71, 275, 144),
|
||||
color=(213, 213, 208),
|
||||
button=(146, 91, 255, 124),
|
||||
),
|
||||
)
|
||||
DISPATCHED = ButtonWrapper(
|
||||
name='DISPATCHED',
|
||||
cn=Button(
|
||||
@ -40,14 +30,14 @@ ENTRY_LOADED = ButtonWrapper(
|
||||
button=(467, 235, 498, 619),
|
||||
),
|
||||
)
|
||||
EXP_MATERIALS_CREDITS = ButtonWrapper(
|
||||
name='EXP_MATERIALS_CREDITS',
|
||||
LOCKED = ButtonWrapper(
|
||||
name='LOCKED',
|
||||
share=Button(
|
||||
file='./assets/share/assignment/ui/EXP_MATERIALS_CREDITS.png',
|
||||
area=(310, 85, 447, 134),
|
||||
search=(290, 65, 467, 154),
|
||||
color=(214, 214, 210),
|
||||
button=(310, 85, 447, 134),
|
||||
file='./assets/share/assignment/ui/LOCKED.png',
|
||||
area=(1051, 480, 1237, 630),
|
||||
search=(1031, 460, 1257, 650),
|
||||
color=(53, 48, 40),
|
||||
button=(1051, 480, 1237, 630),
|
||||
),
|
||||
)
|
||||
OCR_ASSIGNMENT_ENTRY_LIST = ButtonWrapper(
|
||||
@ -64,10 +54,10 @@ OCR_ASSIGNMENT_GROUP_LIST = ButtonWrapper(
|
||||
name='OCR_ASSIGNMENT_GROUP_LIST',
|
||||
share=Button(
|
||||
file='./assets/share/assignment/ui/OCR_ASSIGNMENT_GROUP_LIST.png',
|
||||
area=(106, 70, 848, 135),
|
||||
search=(86, 50, 868, 155),
|
||||
color=(73, 72, 70),
|
||||
button=(106, 70, 848, 135),
|
||||
area=(116, 81, 827, 131),
|
||||
search=(96, 61, 847, 151),
|
||||
color=(80, 79, 77),
|
||||
button=(116, 81, 827, 131),
|
||||
),
|
||||
)
|
||||
OCR_ASSIGNMENT_LIMIT = ButtonWrapper(
|
||||
@ -90,13 +80,3 @@ OCR_ASSIGNMENT_TIME = ButtonWrapper(
|
||||
button=(605, 564, 886, 589),
|
||||
),
|
||||
)
|
||||
SYNTHESIS_MATERIALS = ButtonWrapper(
|
||||
name='SYNTHESIS_MATERIALS',
|
||||
share=Button(
|
||||
file='./assets/share/assignment/ui/SYNTHESIS_MATERIALS.png',
|
||||
area=(521, 91, 603, 128),
|
||||
search=(501, 71, 623, 148),
|
||||
color=(208, 208, 203),
|
||||
button=(521, 91, 603, 128),
|
||||
),
|
||||
)
|
||||
|
@ -1,17 +1,12 @@
|
||||
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_ui import (
|
||||
DISPATCHED,
|
||||
OCR_ASSIGNMENT_TIME,
|
||||
)
|
||||
from tasks.assignment.assets.assets_assignment_dispatch import EMPTY_SLOT
|
||||
from tasks.assignment.assets.assets_assignment_ui import DISPATCHED, LOCKED
|
||||
from tasks.assignment.claim import AssignmentClaim
|
||||
from tasks.assignment.keywords import (
|
||||
AssignmentEntry,
|
||||
KEYWORDS_ASSIGNMENT_GROUP,
|
||||
)
|
||||
from tasks.assignment.keywords import (KEYWORDS_ASSIGNMENT_GROUP,
|
||||
AssignmentEntry, AssignmentEventGroup)
|
||||
from tasks.base.page import page_assignment, page_menu
|
||||
from tasks.battle_pass.keywords import KEYWORD_BATTLE_PASS_QUEST
|
||||
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
|
||||
@ -19,7 +14,7 @@ from tasks.daily.synthesize import SynthesizeUI
|
||||
|
||||
|
||||
class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
def run(self, assignments: list[AssignmentEntry] = None, duration: int = None):
|
||||
def run(self, assignments: list[AssignmentEntry] = None, duration: int = None, event_first: bool = None):
|
||||
self.config.update_battle_pass_quests()
|
||||
self.config.update_daily_quests()
|
||||
|
||||
@ -35,11 +30,22 @@ 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_WhenEventAssignmentsArePresent == 'event_first'
|
||||
|
||||
self.dispatched = dict()
|
||||
self.has_new_dispatch = False
|
||||
self.ensure_scroll_top(page_menu)
|
||||
self.ui_ensure(page_assignment)
|
||||
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()
|
||||
@ -61,7 +67,8 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
quests = self.config.stored.BattlePassTodayQuest.load_quests()
|
||||
if self.has_new_dispatch:
|
||||
if KEYWORD_BATTLE_PASS_QUEST.Dispatch_1_assignments in quests:
|
||||
logger.info('Achieved battle pass quest Dispatch_1_assignments')
|
||||
logger.info(
|
||||
'Achieved battle pass quest Dispatch_1_assignments')
|
||||
self.config.task_call('BattlePass')
|
||||
# Check daily
|
||||
quests = self.config.stored.DailyQuest.load_quests()
|
||||
@ -93,6 +100,8 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
f'User specified assignments: {", ".join([x.name for x in assignments])}')
|
||||
_, remain, _ = self._limit_status
|
||||
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)
|
||||
@ -100,8 +109,8 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
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)
|
||||
self.dispatched[assignment] = datetime.now() + \
|
||||
self._get_assignment_time()
|
||||
continue
|
||||
if remain > 0:
|
||||
self.dispatch(assignment, duration)
|
||||
@ -123,9 +132,7 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
return remain
|
||||
for group in self._iter_groups():
|
||||
self.goto_group(group)
|
||||
entries = self._iter_entries()
|
||||
for _ in range(len(group.entries)):
|
||||
assignment = next(entries)
|
||||
for assignment in self._iter_entries():
|
||||
if assignment in self.dispatched:
|
||||
continue
|
||||
logger.hr('Assignment all', level=2)
|
||||
@ -136,8 +143,8 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
remain += 1
|
||||
continue
|
||||
if self.appear(DISPATCHED):
|
||||
self.dispatched[assignment] = datetime.now() + Duration(
|
||||
OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image)
|
||||
self.dispatched[assignment] = datetime.now() + \
|
||||
self._get_assignment_time()
|
||||
if total == len(self.dispatched):
|
||||
return remain
|
||||
continue
|
||||
@ -174,3 +181,27 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
remain -= 1
|
||||
if remain <= 0:
|
||||
return
|
||||
|
||||
def _dispatch_event(self, remain: int):
|
||||
if remain <= 0:
|
||||
return remain
|
||||
logger.hr('Assignment dispatch event', level=1)
|
||||
for group in self._iter_groups():
|
||||
if not isinstance(group, AssignmentEventGroup):
|
||||
continue
|
||||
self.goto_group(group)
|
||||
for assignment in self._iter_entries():
|
||||
if assignment in self.dispatched:
|
||||
continue
|
||||
logger.hr('Assignment event', level=2)
|
||||
logger.info(f'Check assignment event: {assignment}')
|
||||
self.goto_entry(assignment)
|
||||
if self.appear(LOCKED):
|
||||
logger.info('Assignment is locked')
|
||||
break
|
||||
if self.appear(EMPTY_SLOT):
|
||||
self.dispatch(assignment, None)
|
||||
remain -= 1
|
||||
if remain <= 0:
|
||||
return remain
|
||||
return remain
|
||||
|
@ -41,7 +41,7 @@ class AssignmentClaim(AssignmentDispatch):
|
||||
"""
|
||||
Pages:
|
||||
in: CLAIM
|
||||
out: REDISPATCH
|
||||
out: REPORT
|
||||
"""
|
||||
skip_first_screenshot = True
|
||||
while 1:
|
||||
@ -50,7 +50,9 @@ class AssignmentClaim(AssignmentDispatch):
|
||||
else:
|
||||
self.device.screenshot()
|
||||
# End
|
||||
if self.appear(REDISPATCH):
|
||||
# Neither CLOSE_REPORT nor REDISPATCH is shown
|
||||
# If it is an EVENT assignment
|
||||
if self.appear(REPORT):
|
||||
logger.info('Assignment report appears')
|
||||
break
|
||||
# Claim rewards
|
||||
@ -63,7 +65,7 @@ class AssignmentClaim(AssignmentDispatch):
|
||||
should_redispatch (bool): determined by user config and duration in report
|
||||
|
||||
Pages:
|
||||
in: CLOSE_REPORT and REDISPATCH
|
||||
in: REPORT
|
||||
out: page_assignment
|
||||
"""
|
||||
click_button = REDISPATCH if should_redispatch else CLOSE_REPORT
|
||||
@ -80,6 +82,9 @@ class AssignmentClaim(AssignmentDispatch):
|
||||
# Close report
|
||||
if self.appear_then_click(click_button, interval=2):
|
||||
continue
|
||||
# Only for EVENT assignments
|
||||
if self.appear_then_click(REPORT, interval=2):
|
||||
continue
|
||||
|
||||
def _is_duration_expected(self, duration: int) -> bool:
|
||||
"""
|
||||
|
@ -47,20 +47,24 @@ class AssignmentDispatch(AssignmentUI):
|
||||
dispatched: dict[AssignmentEntry, datetime] = dict()
|
||||
has_new_dispatch: bool = False
|
||||
|
||||
def dispatch(self, assignment: AssignmentEntry, duration: int):
|
||||
def dispatch(self, assignment: AssignmentEntry, duration: int | None):
|
||||
"""
|
||||
Dispatch assignment.
|
||||
Should be called only when limit is checked
|
||||
|
||||
Args:
|
||||
assignment (AssignmentEntry):
|
||||
duration (int): user specified duration
|
||||
duration (int | None): user specified duration, None for event assignments
|
||||
|
||||
Pages:
|
||||
in: EMPTY_SLOT
|
||||
out: DISPATCHED
|
||||
"""
|
||||
self._select_characters()
|
||||
if isinstance(assignment, AssignmentEventEntry):
|
||||
self._select_support()
|
||||
duration = self._get_assignment_time().total_seconds() / 3600
|
||||
else:
|
||||
self._select_duration(duration)
|
||||
self._confirm_assignment()
|
||||
self._wait_until_assignment_started()
|
||||
@ -103,6 +107,30 @@ class AssignmentDispatch(AssignmentUI):
|
||||
if not self.image_color_count(CHARACTER_2_SELECTED, (240, 240, 240)):
|
||||
self.device.click(CHARACTER_2)
|
||||
|
||||
def _select_support(self):
|
||||
skip_first_screenshot = True
|
||||
self.interval_clear(
|
||||
(CHARACTER_SUPPORT_LIST, CHARACTER_SUPPORT_SELECTED), interval=2)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
# End
|
||||
if self.match_color(CHARACTER_SUPPORT_SELECTED):
|
||||
logger.info('Support character is selected')
|
||||
break
|
||||
# Ensure support list
|
||||
if not self.appear(CHARACTER_SUPPORT_LIST):
|
||||
if self.interval_is_reached(CHARACTER_SUPPORT_LIST, interval=2):
|
||||
self.interval_reset(CHARACTER_SUPPORT_LIST, interval=2)
|
||||
self.device.click(EMPTY_SLOT_SUPPORT)
|
||||
continue
|
||||
# Select
|
||||
if self.interval_is_reached(CHARACTER_SUPPORT_SELECTED, interval=2):
|
||||
self.interval_reset(CHARACTER_SUPPORT_SELECTED, interval=2)
|
||||
self.device.click(CHARACTER_SUPPORT)
|
||||
|
||||
def _select_duration(self, duration: int):
|
||||
if duration not in {4, 8, 12, 20}:
|
||||
logger.warning(
|
||||
|
@ -1,6 +1,8 @@
|
||||
import tasks.assignment.keywords.entry as KEYWORDS_ASSIGNMENT_ENTRY
|
||||
import tasks.assignment.keywords.group as KEYWORDS_ASSIGNMENT_GROUP
|
||||
from tasks.assignment.keywords.classes import AssignmentEntry, AssignmentGroup
|
||||
import tasks.assignment.keywords.event_entry as KEYWORDS_ASSIGNMENT_EVENT_ENTRY
|
||||
import tasks.assignment.keywords.event_group as KEYWORDS_ASSIGNMENT_EVENT_GROUP
|
||||
from tasks.assignment.keywords.classes import *
|
||||
|
||||
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials.entries = (
|
||||
KEYWORDS_ASSIGNMENT_ENTRY.Nine_Billion_Names,
|
||||
@ -23,10 +25,37 @@ KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials.entries = (
|
||||
KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master,
|
||||
KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity,
|
||||
)
|
||||
KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force.entries = (
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Repulsion_Bridge_Errors,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Meal_Delivery_Robot_Check_Up,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Noise_Complaint,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Interior_Temperature_Modulator,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Researcher_Health_Reports,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Confidential_Investigation,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Borrowed_Equipment,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Booking_System,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Non_Digital_Documents,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Drip_Feed_Errors,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Pet_Movement_Route_Planning,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Food_Improvement_Plan,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Curio_Distribution,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Super_Urgent_Waiting_Online,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Ventilation_Problem,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Unstable_Connection,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Chronology_Checks,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Supply_Chain_Management,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Malicious_Occupation_of_Public_Space,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Uniform_Material,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Virus_Re_creation_Report,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Abnormal_Signal,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Flexible_Working_Approval,
|
||||
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Lighting_Issue,
|
||||
)
|
||||
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,
|
||||
):
|
||||
for entry in group.entries:
|
||||
assert entry.group is None
|
||||
|
@ -15,6 +15,19 @@ class AssignmentGroup(Keyword):
|
||||
class AssignmentEntry(Keyword):
|
||||
instances: ClassVar = {}
|
||||
group: AssignmentGroup = None
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class AssignmentEventGroup(AssignmentGroup):
|
||||
instances: ClassVar = {}
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class AssignmentEventEntry(AssignmentEntry):
|
||||
instances: ClassVar = {}
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return super().__hash__()
|
||||
|
197
tasks/assignment/keywords/event_entry.py
Normal file
@ -0,0 +1,197 @@
|
||||
from .classes import AssignmentEventEntry
|
||||
|
||||
# This file was auto-generated, do not modify it manually. To generate:
|
||||
# ``` python -m dev_tools.keyword_extract ```
|
||||
|
||||
Repulsion_Bridge_Errors = AssignmentEventEntry(
|
||||
id=1,
|
||||
name='Repulsion_Bridge_Errors',
|
||||
cn='斥力桥报错',
|
||||
cht='斥力橋錯誤',
|
||||
en='Repulsion Bridge Errors',
|
||||
jp='斥力ブリッジエラー',
|
||||
)
|
||||
Meal_Delivery_Robot_Check_Up = AssignmentEventEntry(
|
||||
id=2,
|
||||
name='Meal_Delivery_Robot_Check_Up',
|
||||
cn='送餐机器人检修',
|
||||
cht='送餐機器人檢修',
|
||||
en='Meal-Delivery Robot Check-Up',
|
||||
jp='配膳ロボット点検修理',
|
||||
)
|
||||
Noise_Complaint = AssignmentEventEntry(
|
||||
id=3,
|
||||
name='Noise_Complaint',
|
||||
cn='噪音投诉问题',
|
||||
cht='噪音投訴問題',
|
||||
en='Noise Complaint',
|
||||
jp='騒音苦情問題',
|
||||
)
|
||||
Interior_Temperature_Modulator = AssignmentEventEntry(
|
||||
id=4,
|
||||
name='Interior_Temperature_Modulator',
|
||||
cn='室内温度调节器',
|
||||
cht='室內溫度調節器',
|
||||
en='Interior Temperature Modulator',
|
||||
jp='室内温度調節器',
|
||||
)
|
||||
Researcher_Health_Reports = AssignmentEventEntry(
|
||||
id=5,
|
||||
name='Researcher_Health_Reports',
|
||||
cn='科员的体检报告',
|
||||
cht='組員的體檢報告',
|
||||
en="Researchers' Health Reports",
|
||||
jp='スタッフの健康診断報告',
|
||||
)
|
||||
Confidential_Investigation = AssignmentEventEntry(
|
||||
id=6,
|
||||
name='Confidential_Investigation',
|
||||
cn='秘密调查行动',
|
||||
cht='秘密調查行動',
|
||||
en='Confidential Investigation',
|
||||
jp='秘密裏の調査',
|
||||
)
|
||||
Borrowed_Equipment = AssignmentEventEntry(
|
||||
id=7,
|
||||
name='Borrowed_Equipment',
|
||||
cn='实验器械借用',
|
||||
cht='實驗器械借用',
|
||||
en='Borrowed Equipment',
|
||||
jp='実験機器借用',
|
||||
)
|
||||
Booking_System = AssignmentEventEntry(
|
||||
id=8,
|
||||
name='Booking_System',
|
||||
cn='会议室预约系统',
|
||||
cht='會議室預約系統',
|
||||
en='Booking System',
|
||||
jp='会議室予約システム',
|
||||
)
|
||||
Non_Digital_Documents = AssignmentEventEntry(
|
||||
id=9,
|
||||
name='Non_Digital_Documents',
|
||||
cn='非电子版文件',
|
||||
cht='非電子版文件',
|
||||
en='Non-Digital Documents',
|
||||
jp='非デジタル版ファイル',
|
||||
)
|
||||
Drip_Feed_Errors = AssignmentEventEntry(
|
||||
id=10,
|
||||
name='Drip_Feed_Errors',
|
||||
cn='液滴系统报错',
|
||||
cht='液滴系統錯誤',
|
||||
en='Drip-Feed Errors',
|
||||
jp='水やりシステムエラー',
|
||||
)
|
||||
Pet_Movement_Route_Planning = AssignmentEventEntry(
|
||||
id=11,
|
||||
name='Pet_Movement_Route_Planning',
|
||||
cn='宠物行动路线规划',
|
||||
cht='寵物行動路線規劃',
|
||||
en='Pet Movement Route Planning',
|
||||
jp='ペットの行動ルート規制',
|
||||
)
|
||||
Food_Improvement_Plan = AssignmentEventEntry(
|
||||
id=12,
|
||||
name='Food_Improvement_Plan',
|
||||
cn='餐饮优化方案',
|
||||
cht='餐飲改良方案',
|
||||
en='Food Improvement Plan',
|
||||
jp='飲食優良化法案',
|
||||
)
|
||||
Curio_Distribution = AssignmentEventEntry(
|
||||
id=13,
|
||||
name='Curio_Distribution',
|
||||
cn='奇物借用问题',
|
||||
cht='奇物借用問題',
|
||||
en='Curio Distribution',
|
||||
jp='奇物借用問題',
|
||||
)
|
||||
Super_Urgent_Waiting_Online = AssignmentEventEntry(
|
||||
id=14,
|
||||
name='Super_Urgent_Waiting_Online',
|
||||
cn='来活人很急在线等',
|
||||
cht='急,線上等',
|
||||
en='Super Urgent, Waiting Online',
|
||||
jp='緊急助っ人求むオンラインにて待つ',
|
||||
)
|
||||
Ventilation_Problem = AssignmentEventEntry(
|
||||
id=15,
|
||||
name='Ventilation_Problem',
|
||||
cn='空气流通问题',
|
||||
cht='空氣流通問題',
|
||||
en='Ventilation Problem',
|
||||
jp='換気問題',
|
||||
)
|
||||
Unstable_Connection = AssignmentEventEntry(
|
||||
id=16,
|
||||
name='Unstable_Connection',
|
||||
cn='连接不稳定问题',
|
||||
cht='連線不穩定問題',
|
||||
en='Unstable Connection',
|
||||
jp='接続不安定問題',
|
||||
)
|
||||
Chronology_Checks = AssignmentEventEntry(
|
||||
id=17,
|
||||
name='Chronology_Checks',
|
||||
cn='编年史校对',
|
||||
cht='編年史校對',
|
||||
en='Chronology Checks',
|
||||
jp='編年史校正',
|
||||
)
|
||||
Supply_Chain_Management = AssignmentEventEntry(
|
||||
id=18,
|
||||
name='Supply_Chain_Management',
|
||||
cn='物流供应链管理',
|
||||
cht='物流供應鏈管理',
|
||||
en='Supply Chain Management',
|
||||
jp='物流供給路線管理',
|
||||
)
|
||||
Malicious_Occupation_of_Public_Space = AssignmentEventEntry(
|
||||
id=19,
|
||||
name='Malicious_Occupation_of_Public_Space',
|
||||
cn='公共区域被恶意侵占',
|
||||
cht='公共區域被惡意侵佔',
|
||||
en='Malicious Occupation of Public Space',
|
||||
jp='公共区域の悪意による独占',
|
||||
)
|
||||
Uniform_Material = AssignmentEventEntry(
|
||||
id=20,
|
||||
name='Uniform_Material',
|
||||
cn='科室服装面料',
|
||||
cht='科室服裝材質',
|
||||
en='Uniform Material',
|
||||
jp='スタッフ制服の素材',
|
||||
)
|
||||
Virus_Re_creation_Report = AssignmentEventEntry(
|
||||
id=21,
|
||||
name='Virus_Re_creation_Report',
|
||||
cn='病毒溯源报告',
|
||||
cht='病毒溯源報告',
|
||||
en='Virus Re-creation Report',
|
||||
jp='ウイルス根源報告',
|
||||
)
|
||||
Abnormal_Signal = AssignmentEventEntry(
|
||||
id=22,
|
||||
name='Abnormal_Signal',
|
||||
cn='舱段信号异常',
|
||||
cht='艙段訊號異常',
|
||||
en='Abnormal Signal',
|
||||
jp='部分の信号異常',
|
||||
)
|
||||
Flexible_Working_Approval = AssignmentEventEntry(
|
||||
id=23,
|
||||
name='Flexible_Working_Approval',
|
||||
cn='轮休审批流程',
|
||||
cht='輪休審批流程',
|
||||
en='Flexible Working Approval',
|
||||
jp='交代休み審査フロー',
|
||||
)
|
||||
Lighting_Issue = AssignmentEventEntry(
|
||||
id=24,
|
||||
name='Lighting_Issue',
|
||||
cn='灯光照明问题',
|
||||
cht='燈光照明問題',
|
||||
en='Lighting Issue',
|
||||
jp='照明の色問題',
|
||||
)
|
13
tasks/assignment/keywords/event_group.py
Normal file
@ -0,0 +1,13 @@
|
||||
from .classes import AssignmentEventGroup
|
||||
|
||||
# This file was auto-generated, do not modify it manually. To generate:
|
||||
# ``` python -m dev_tools.keyword_extract ```
|
||||
|
||||
Space_Station_Task_Force = AssignmentEventGroup(
|
||||
id=1,
|
||||
name='Space_Station_Task_Force',
|
||||
cn='空间站特派',
|
||||
cht='太空站特派',
|
||||
en='Space Station Task Force',
|
||||
jp='ステーション特派',
|
||||
)
|
@ -1,6 +1,7 @@
|
||||
import re
|
||||
from collections.abc import Iterator
|
||||
from datetime import timedelta
|
||||
from functools import cached_property
|
||||
from typing import Iterator
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.exception import ScriptError
|
||||
@ -26,6 +27,14 @@ class AssignmentOcr(Ocr):
|
||||
(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_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')
|
||||
]
|
||||
}
|
||||
|
||||
@ -39,7 +48,15 @@ class AssignmentOcr(Ocr):
|
||||
def filter_detected(self, result) -> bool:
|
||||
# Drop duration rows
|
||||
res = Duration.timedelta_regex(self.lang).search(result.ocr_text)
|
||||
return not bool(res.group('seconds'))
|
||||
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
|
||||
|
||||
def after_process(self, result: str):
|
||||
result = super().after_process(result)
|
||||
@ -50,7 +67,17 @@ class AssignmentOcr(Ocr):
|
||||
if matched is None:
|
||||
return result
|
||||
keyword_lang = self.lang
|
||||
matched = getattr(KEYWORDS_ASSIGNMENT_ENTRY, matched.lastgroup)
|
||||
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)
|
||||
logger.attr(name=f'{self.name} after_process',
|
||||
text=f'{result} -> {matched}')
|
||||
@ -59,15 +86,16 @@ class AssignmentOcr(Ocr):
|
||||
|
||||
ASSIGNMENT_GROUP_LIST = DraggableList(
|
||||
'AssignmentGroupList',
|
||||
keyword_class=AssignmentGroup,
|
||||
ocr_class=Ocr,
|
||||
keyword_class=[AssignmentGroup, AssignmentEventGroup],
|
||||
ocr_class=AssignmentOcr,
|
||||
search_button=OCR_ASSIGNMENT_GROUP_LIST,
|
||||
check_row_order=False,
|
||||
active_color=(240, 240, 240),
|
||||
drag_direction='right'
|
||||
)
|
||||
ASSIGNMENT_ENTRY_LIST = DraggableList(
|
||||
'AssignmentEntryList',
|
||||
keyword_class=AssignmentEntry,
|
||||
keyword_class=[AssignmentEntry, AssignmentEventEntry],
|
||||
ocr_class=AssignmentOcr,
|
||||
search_button=OCR_ASSIGNMENT_ENTRY_LIST,
|
||||
check_row_order=False,
|
||||
@ -86,7 +114,11 @@ class AssignmentUI(UI):
|
||||
self.device.screenshot()
|
||||
self.goto_group(KEYWORDS_ASSIGNMENT_GROUP.Character_Materials)
|
||||
"""
|
||||
selected = ASSIGNMENT_GROUP_LIST.get_selected_row(self)
|
||||
if selected and selected.matched_keyword == group:
|
||||
return
|
||||
logger.hr('Assignment group goto', level=3)
|
||||
self._wait_until_group_loaded()
|
||||
if ASSIGNMENT_GROUP_LIST.select_row(group, self):
|
||||
self._wait_until_entry_loaded()
|
||||
|
||||
@ -112,6 +144,23 @@ class AssignmentUI(UI):
|
||||
self.goto_group(entry.group)
|
||||
ASSIGNMENT_ENTRY_LIST.select_row(entry, self)
|
||||
|
||||
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
|
||||
|
||||
def _wait_until_entry_loaded(self):
|
||||
skip_first_screenshot = True
|
||||
timeout = Timer(2, count=3).start()
|
||||
@ -142,8 +191,13 @@ class AssignmentUI(UI):
|
||||
self.config.stored.Assignment.set(0, 0)
|
||||
return current, remain, total
|
||||
|
||||
def _get_assignment_time(self) -> timedelta:
|
||||
return Duration(OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image)
|
||||
|
||||
def _iter_groups(self) -> Iterator[AssignmentGroup]:
|
||||
ASSIGNMENT_GROUP_LIST.load_rows(main=self)
|
||||
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
|
||||
|
||||
@ -152,5 +206,8 @@ class AssignmentUI(UI):
|
||||
Iterate entries from top to bottom
|
||||
"""
|
||||
ASSIGNMENT_ENTRY_LIST.load_rows(main=self)
|
||||
for button in ASSIGNMENT_ENTRY_LIST.cur_buttons:
|
||||
yield button.matched_keyword
|
||||
# Freeze ocr results here
|
||||
yield from [
|
||||
button.matched_keyword
|
||||
for button in ASSIGNMENT_ENTRY_LIST.cur_buttons
|
||||
]
|
||||
|