Add: Support for event assignments

This commit is contained in:
Zebartin 2023-09-26 15:06:43 +08:00
parent ad86cc6f6c
commit 81d785852d
28 changed files with 503 additions and 87 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -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 = {

View File

@ -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)

View File

@ -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

View File

@ -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),
),
)

View File

@ -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),
),
)

View File

@ -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),
),
)

View File

@ -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

View File

@ -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:
"""

View File

@ -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(

View File

@ -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

View File

@ -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__()

View 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='照明の色問題',
)

View 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='ステーション特派',
)

View File

@ -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
]