Merge pull request #113 from Zebartin/dev

Add: Event assignments (Space Station Task Force)
This commit is contained in:
LmeSzinc 2023-09-27 22:38:21 +08:00 committed by GitHub
commit 28081361d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 827 additions and 144 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

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: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 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: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 5.3 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: 39 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: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -139,6 +139,7 @@
"Name_3": "The_Invisible_Hand", "Name_3": "The_Invisible_Hand",
"Name_4": "Nine_Billion_Names", "Name_4": "Nine_Billion_Names",
"Duration": 20, "Duration": 20,
"Event": true,
"Assignment": {} "Assignment": {}
} }
}, },

View File

@ -200,6 +200,8 @@ def generate_code():
output = os.path.join(path, 'assets') output = os.path.join(path, 'assets')
os.makedirs(output, exist_ok=True) os.makedirs(output, exist_ok=True)
for prev in iter_folder(output, ext='.py'): for prev in iter_folder(output, ext='.py'):
if os.path.basename(prev) == '__init__.py':
continue
os.remove(prev) os.remove(prev)
for module, module_data in all.items(): for module, module_data in all.items():

View File

@ -1,7 +1,6 @@
import os import os
import re import re
import typing as t import typing as t
from collections import namedtuple
from functools import cached_property from functools import cached_property
from module.base.code_generator import CodeGenerator from module.base.code_generator import CodeGenerator
@ -254,14 +253,19 @@ class KeywordExtract:
self.clear_keywords() self.clear_keywords()
def generate_assignment_keywords(self): def generate_assignment_keywords(self):
KeywordFromFile = namedtuple('KeywordFromFile', ('file', 'class_name', 'output_file')) self.load_keywords(['空间站特派'])
for keyword in ( self.write_keywords(
KeywordFromFile('ExpeditionGroup.json', 'AssignmentGroup', './tasks/assignment/keywords/group.py'), keyword_class='AssignmentEventGroup',
KeywordFromFile('ExpeditionData.json', 'AssignmentEntry', './tasks/assignment/keywords/entry.py') 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.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): def generate_map_planes(self):
planes = { planes = {

View File

@ -1046,6 +1046,10 @@
20 20
] ]
}, },
"Event": {
"type": "checkbox",
"value": true
},
"Assignment": { "Assignment": {
"type": "stored", "type": "stored",
"value": {}, "value": {},

View File

@ -176,6 +176,7 @@ Assignment:
Duration: Duration:
value: 20 value: 20
option: [ 4, 8, 12, 20 ] option: [ 4, 8, 12, 20 ]
Event: true
Assignment: Assignment:
stored: StoredAssignment stored: StoredAssignment
order: 3 order: 3

View File

@ -106,6 +106,7 @@ class GeneratedConfig:
Assignment_Name_3 = 'The_Invisible_Hand' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity Assignment_Name_3 = 'The_Invisible_Hand' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity
Assignment_Name_4 = 'Nine_Billion_Names' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity Assignment_Name_4 = 'Nine_Billion_Names' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity
Assignment_Duration = 20 # 4, 8, 12, 20 Assignment_Duration = 20 # 4, 8, 12, 20
Assignment_Event = True
Assignment_Assignment = {} Assignment_Assignment = {}
# Group `ItemStorage` # Group `ItemStorage`

View File

@ -728,6 +728,10 @@
"12": "12 Hours", "12": "12 Hours",
"20": "20 Hours" "20": "20 Hours"
}, },
"Event": {
"name": "Complete Event Assignments First",
"help": ""
},
"Assignment": { "Assignment": {
"name": "Assign.", "name": "Assign.",
"help": "" "help": ""

View File

@ -728,6 +728,10 @@
"12": "12 horas", "12": "12 horas",
"20": "20 horas" "20": "20 horas"
}, },
"Event": {
"name": "Assignment.Event.name",
"help": ""
},
"Assignment": { "Assignment": {
"name": "Encargo", "name": "Encargo",
"help": "" "help": ""

View File

@ -728,6 +728,10 @@
"12": "12", "12": "12",
"20": "20" "20": "20"
}, },
"Event": {
"name": "Assignment.Event.name",
"help": ""
},
"Assignment": { "Assignment": {
"name": "Assignment.Assignment.name", "name": "Assignment.Assignment.name",
"help": "Assignment.Assignment.help" "help": "Assignment.Assignment.help"

View File

@ -728,6 +728,10 @@
"12": "12小时", "12": "12小时",
"20": "20小时" "20": "20小时"
}, },
"Event": {
"name": "有活动时优先做活动委托",
"help": ""
},
"Assignment": { "Assignment": {
"name": "委托", "name": "委托",
"help": "" "help": ""

View File

@ -728,6 +728,10 @@
"12": "12小時", "12": "12小時",
"20": "20小時" "20": "20小時"
}, },
"Event": {
"name": "有活動時優先做活動委託",
"help": ""
},
"Assignment": { "Assignment": {
"name": "委託", "name": "委託",
"help": "" "help": ""

View File

@ -375,17 +375,17 @@ class Duration(Ocr):
def timedelta_regex(cls, lang): def timedelta_regex(cls, lang):
regex_str = { regex_str = {
'cn': r'^(?P<prefix>.*?)' 'cn': r'^(?P<prefix>.*?)'
r'((?P<days>\d{1,2})天)?' r'((?P<days>\d{1,2})\s*\s*)?'
r'((?P<hours>\d{1,2})小时)?' r'((?P<hours>\d{1,2})\s*小时\s*)?'
r'((?P<minutes>\d{1,2})分钟)?' r'((?P<minutes>\d{1,2})\s*分钟\s*)?'
r'((?P<seconds>\d{1,2})秒)?' r'((?P<seconds>\d{1,2})\s*秒)?'
r'$', r'(?P<suffix>[^天时钟秒]*?)$',
'en': r'^(?P<prefix>.*?)' 'en': r'^(?P<prefix>.*?)'
r'((?P<days>\d{1,2})\s*d\s*)?' r'((?P<days>\d{1,2})\s*d\s*)?'
r'((?P<hours>\d{1,2})\s*h\s*)?' r'((?P<hours>\d{1,2})\s*h\s*)?'
r'((?P<minutes>\d{1,2})\s*m\s*)?' r'((?P<minutes>\d{1,2})\s*m\s*)?'
r'((?P<seconds>\d{1,2})\s*s)?' r'((?P<seconds>\d{1,2})\s*s)?'
r'$' r'(?P<suffix>[^dhms]*?)$'
}[lang] }[lang]
return re.compile(regex_str) return re.compile(regex_str)

View File

@ -0,0 +1,18 @@
from module.config.server import VALID_LANG
from tasks.assignment.assets.assets_assignment_ui import (
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)
for group_button_wrapper in (
SPACE_STATION_TASK_FORCE_CHECK, SPACE_STATION_TASK_FORCE_CLICK,
CHARACTER_MATERIALS_CHECK, CHARACTER_MATERIALS_CLICK,
EXP_MATERIALS_CREDITS_CHECK, EXP_MATERIALS_CREDITS_CLICK,
SYNTHESIS_MATERIALS_CHECK, SYNTHESIS_MATERIALS_CLICK,
):
for lang in VALID_LANG:
button = getattr(group_button_wrapper, lang, None)
if button is None:
continue
button.search = GROUP_SEARCH.button

View File

@ -43,3 +43,20 @@ REDISPATCH = ButtonWrapper(
button=(779, 592, 905, 629), button=(779, 592, 905, 629),
), ),
) )
REPORT = ButtonWrapper(
name='REPORT',
cn=Button(
file='./assets/cn/assignment/claim/REPORT.png',
area=(537, 80, 742, 128),
search=(517, 60, 762, 148),
color=(102, 90, 68),
button=(537, 80, 742, 128),
),
en=Button(
file='./assets/en/assignment/claim/REPORT.png',
area=(393, 83, 885, 137),
search=(373, 63, 905, 157),
color=(71, 63, 49),
button=(393, 83, 885, 137),
),
)

View File

@ -8,14 +8,14 @@ ASSIGNMENT_START = ButtonWrapper(
cn=Button( cn=Button(
file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.png', file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.png',
area=(581, 321, 699, 349), area=(581, 321, 699, 349),
search=(561, 301, 719, 369), search=(573, 299, 707, 412),
color=(93, 84, 66), color=(93, 84, 66),
button=(581, 321, 699, 349), button=(581, 321, 699, 349),
), ),
en=Button( en=Button(
file='./assets/en/assignment/dispatch/ASSIGNMENT_START.png', file='./assets/en/assignment/dispatch/ASSIGNMENT_START.png',
area=(679, 323, 784, 347), area=(679, 323, 784, 347),
search=(659, 303, 804, 367), search=(669, 297, 794, 416),
color=(93, 83, 65), color=(93, 83, 65),
button=(679, 323, 784, 347), button=(679, 323, 784, 347),
), ),
@ -24,10 +24,10 @@ ASSIGNMENT_STARTED_CHECK = ButtonWrapper(
name='ASSIGNMENT_STARTED_CHECK', name='ASSIGNMENT_STARTED_CHECK',
share=Button( share=Button(
file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png', file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png',
area=(1174, 297, 1211, 514), area=(542, 412, 1156, 422),
search=(1154, 277, 1231, 534), search=(522, 392, 1176, 442),
color=(86, 81, 78), color=(232, 230, 226),
button=(1174, 297, 1211, 514), button=(542, 412, 1156, 422),
), ),
) )
CHARACTER_1 = ButtonWrapper( CHARACTER_1 = ButtonWrapper(
@ -87,6 +87,43 @@ CHARACTER_LIST = ButtonWrapper(
button=(91, 163, 136, 180), 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=(69, 65, 80),
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( CONFIRM_ASSIGNMENT = ButtonWrapper(
name='CONFIRM_ASSIGNMENT', name='CONFIRM_ASSIGNMENT',
cn=Button( cn=Button(
@ -149,8 +186,18 @@ EMPTY_SLOT = ButtonWrapper(
share=Button( share=Button(
file='./assets/share/assignment/dispatch/EMPTY_SLOT.png', file='./assets/share/assignment/dispatch/EMPTY_SLOT.png',
area=(1075, 562, 1110, 597), area=(1075, 562, 1110, 597),
search=(1054, 542, 1220, 616), search=(873, 542, 1136, 608),
color=(200, 200, 195), color=(200, 200, 195),
button=(1075, 562, 1110, 597), 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,14 +3,38 @@ from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate: # This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ``` # ``` python -m dev_tools.button_extract ```
CHARACTER_MATERIALS = ButtonWrapper( CHARACTER_MATERIALS_CHECK = ButtonWrapper(
name='CHARACTER_MATERIALS', name='CHARACTER_MATERIALS_CHECK',
share=Button( cn=Button(
file='./assets/share/assignment/ui/CHARACTER_MATERIALS.png', file='./assets/cn/assignment/ui/CHARACTER_MATERIALS_CHECK.png',
area=(146, 91, 255, 124), area=(346, 97, 421, 117),
search=(126, 71, 275, 144), search=(326, 77, 441, 137),
color=(213, 213, 208), color=(177, 176, 173),
button=(146, 91, 255, 124), button=(346, 97, 421, 117),
),
en=Button(
file='./assets/en/assignment/ui/CHARACTER_MATERIALS_CHECK.png',
area=(339, 88, 429, 126),
search=(319, 68, 449, 146),
color=(193, 192, 189),
button=(339, 88, 429, 126),
),
)
CHARACTER_MATERIALS_CLICK = ButtonWrapper(
name='CHARACTER_MATERIALS_CLICK',
cn=Button(
file='./assets/cn/assignment/ui/CHARACTER_MATERIALS_CLICK.png',
area=(347, 97, 421, 117),
search=(327, 77, 441, 137),
color=(60, 60, 60),
button=(347, 97, 421, 117),
),
en=Button(
file='./assets/en/assignment/ui/CHARACTER_MATERIALS_CLICK.png',
area=(339, 88, 429, 127),
search=(319, 68, 449, 147),
color=(49, 49, 49),
button=(339, 88, 429, 127),
), ),
) )
DISPATCHED = ButtonWrapper( DISPATCHED = ButtonWrapper(
@ -40,14 +64,58 @@ ENTRY_LOADED = ButtonWrapper(
button=(467, 235, 498, 619), button=(467, 235, 498, 619),
), ),
) )
EXP_MATERIALS_CREDITS = ButtonWrapper( EXP_MATERIALS_CREDITS_CHECK = ButtonWrapper(
name='EXP_MATERIALS_CREDITS', name='EXP_MATERIALS_CREDITS_CHECK',
cn=Button(
file='./assets/cn/assignment/ui/EXP_MATERIALS_CREDITS_CHECK.png',
area=(514, 97, 614, 117),
search=(494, 77, 634, 137),
color=(178, 177, 174),
button=(514, 97, 614, 117),
),
en=Button(
file='./assets/en/assignment/ui/EXP_MATERIALS_CREDITS_CHECK.png',
area=(529, 88, 599, 126),
search=(509, 68, 619, 146),
color=(202, 201, 198),
button=(529, 88, 599, 126),
),
)
EXP_MATERIALS_CREDITS_CLICK = ButtonWrapper(
name='EXP_MATERIALS_CREDITS_CLICK',
cn=Button(
file='./assets/cn/assignment/ui/EXP_MATERIALS_CREDITS_CLICK.png',
area=(514, 97, 614, 117),
search=(494, 77, 634, 137),
color=(61, 60, 60),
button=(514, 97, 614, 117),
),
en=Button(
file='./assets/en/assignment/ui/EXP_MATERIALS_CREDITS_CLICK.png',
area=(528, 88, 599, 127),
search=(508, 68, 619, 147),
color=(42, 42, 42),
button=(528, 88, 599, 127),
),
)
GROUP_SEARCH = ButtonWrapper(
name='GROUP_SEARCH',
share=Button( share=Button(
file='./assets/share/assignment/ui/EXP_MATERIALS_CREDITS.png', file='./assets/share/assignment/ui/GROUP_SEARCH.png',
area=(310, 85, 447, 134), area=(111, 76, 835, 140),
search=(290, 65, 467, 154), search=(91, 56, 855, 160),
color=(214, 214, 210), color=(82, 79, 77),
button=(310, 85, 447, 134), button=(111, 76, 835, 140),
),
)
OCR_ASSIGNMENT_ENTRY_LIST = ButtonWrapper(
name='OCR_ASSIGNMENT_ENTRY_LIST',
share=Button(
file='./assets/share/assignment/ui/OCR_ASSIGNMENT_ENTRY_LIST.png',
area=(133, 139, 494, 620),
search=(113, 119, 514, 640),
color=(201, 199, 193),
button=(133, 139, 494, 620),
), ),
) )
OCR_ASSIGNMENT_LIMIT = ButtonWrapper( OCR_ASSIGNMENT_LIMIT = ButtonWrapper(
@ -60,16 +128,6 @@ OCR_ASSIGNMENT_LIMIT = ButtonWrapper(
button=(1095, 95, 1180, 119), button=(1095, 95, 1180, 119),
), ),
) )
OCR_ASSIGNMENT_LIST = ButtonWrapper(
name='OCR_ASSIGNMENT_LIST',
share=Button(
file='./assets/share/assignment/ui/OCR_ASSIGNMENT_LIST.png',
area=(133, 139, 494, 620),
search=(113, 119, 514, 640),
color=(201, 199, 193),
button=(133, 139, 494, 620),
),
)
OCR_ASSIGNMENT_TIME = ButtonWrapper( OCR_ASSIGNMENT_TIME = ButtonWrapper(
name='OCR_ASSIGNMENT_TIME', name='OCR_ASSIGNMENT_TIME',
share=Button( share=Button(
@ -80,13 +138,71 @@ OCR_ASSIGNMENT_TIME = ButtonWrapper(
button=(605, 564, 886, 589), button=(605, 564, 886, 589),
), ),
) )
SYNTHESIS_MATERIALS = ButtonWrapper( SPACE_STATION_TASK_FORCE_CHECK = ButtonWrapper(
name='SYNTHESIS_MATERIALS', name='SPACE_STATION_TASK_FORCE_CHECK',
share=Button( cn=Button(
file='./assets/share/assignment/ui/SYNTHESIS_MATERIALS.png', file='./assets/cn/assignment/ui/SPACE_STATION_TASK_FORCE_CHECK.png',
area=(521, 91, 603, 128), area=(157, 97, 249, 117),
search=(501, 71, 623, 148), search=(137, 77, 269, 137),
color=(208, 208, 203), color=(181, 181, 178),
button=(521, 91, 603, 128), button=(157, 97, 249, 117),
),
en=Button(
file='./assets/en/assignment/ui/SPACE_STATION_TASK_FORCE_CHECK.png',
area=(176, 88, 239, 126),
search=(156, 68, 259, 146),
color=(198, 197, 194),
button=(176, 88, 239, 126),
),
)
SPACE_STATION_TASK_FORCE_CLICK = ButtonWrapper(
name='SPACE_STATION_TASK_FORCE_CLICK',
cn=Button(
file='./assets/cn/assignment/ui/SPACE_STATION_TASK_FORCE_CLICK.png',
area=(157, 97, 249, 117),
search=(137, 77, 269, 137),
color=(71, 70, 68),
button=(157, 97, 249, 117),
),
en=Button(
file='./assets/en/assignment/ui/SPACE_STATION_TASK_FORCE_CLICK.png',
area=(176, 88, 239, 126),
search=(156, 68, 259, 146),
color=(60, 58, 56),
button=(176, 88, 239, 126),
),
)
SYNTHESIS_MATERIALS_CHECK = ButtonWrapper(
name='SYNTHESIS_MATERIALS_CHECK',
cn=Button(
file='./assets/cn/assignment/ui/SYNTHESIS_MATERIALS_CHECK.png',
area=(708, 97, 783, 117),
search=(688, 77, 803, 137),
color=(180, 179, 176),
button=(708, 97, 783, 117),
),
en=Button(
file='./assets/en/assignment/ui/SYNTHESIS_MATERIALS_CHECK.png',
area=(703, 88, 790, 126),
search=(683, 68, 810, 146),
color=(189, 188, 185),
button=(703, 88, 790, 126),
),
)
SYNTHESIS_MATERIALS_CLICK = ButtonWrapper(
name='SYNTHESIS_MATERIALS_CLICK',
cn=Button(
file='./assets/cn/assignment/ui/SYNTHESIS_MATERIALS_CLICK.png',
area=(709, 97, 783, 117),
search=(689, 77, 803, 137),
color=(68, 66, 65),
button=(709, 97, 783, 117),
),
en=Button(
file='./assets/en/assignment/ui/SYNTHESIS_MATERIALS_CLICK.png',
area=(702, 88, 790, 126),
search=(682, 68, 810, 146),
color=(61, 59, 58),
button=(702, 88, 790, 126),
), ),
) )

View File

@ -1,17 +1,10 @@
from datetime import datetime from datetime import datetime
from module.logger import logger 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.claim import AssignmentClaim from tasks.assignment.claim import AssignmentClaim
from tasks.assignment.keywords import ( from tasks.assignment.keywords import (KEYWORDS_ASSIGNMENT_GROUP,
AssignmentEntry, AssignmentEntry, AssignmentEventGroup)
KEYWORDS_ASSIGNMENT_GROUP, from tasks.assignment.ui import AssignmentStatus
)
from tasks.base.page import page_assignment, page_menu from tasks.base.page import page_assignment, page_menu
from tasks.battle_pass.keywords import KEYWORD_BATTLE_PASS_QUEST from tasks.battle_pass.keywords import KEYWORD_BATTLE_PASS_QUEST
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
@ -19,7 +12,7 @@ from tasks.daily.synthesize import SynthesizeUI
class Assignment(AssignmentClaim, 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_battle_pass_quests()
self.config.update_daily_quests() self.config.update_daily_quests()
@ -35,14 +28,26 @@ class Assignment(AssignmentClaim, SynthesizeUI):
'There are duplicate assignments in config, check it out') 'There are duplicate assignments in config, check it out')
if duration is None: if duration is None:
duration = self.config.Assignment_Duration duration = self.config.Assignment_Duration
if event_first is None:
event_first = self.config.Assignment_Event
self.dispatched = dict() self.dispatched = dict()
self.has_new_dispatch = False self.has_new_dispatch = False
self.ensure_scroll_top(page_menu) self.ensure_scroll_top(page_menu)
self.ui_ensure(page_assignment) self.ui_ensure(page_assignment)
# Iterate in user-specified order, return undispatched ones event_ongoing = next((
undispatched = list(self._check_inlist(assignments, duration)) g for g in self._iter_groups()
remain = self._check_all() 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()
undispatched = [x for x in undispatched if x not in self.dispatched]
# There are unchecked assignments # There are unchecked assignments
if remain > 0: if remain > 0:
for assignment in undispatched[:remain]: for assignment in undispatched[:remain]:
@ -53,7 +58,8 @@ class Assignment(AssignmentClaim, SynthesizeUI):
f'{", ".join([x.name for x in undispatched[remain:]])}') f'{", ".join([x.name for x in undispatched[remain:]])}')
elif remain > len(undispatched): elif remain > len(undispatched):
self._dispatch_remain(duration, remain - len(undispatched)) self._dispatch_remain(duration, remain - len(undispatched))
# Refresh dashboard before return
_ = self._limit_status
# Scheduler # Scheduler
logger.attr('has_new_dispatch', self.has_new_dispatch) logger.attr('has_new_dispatch', self.has_new_dispatch)
with self.config.multi_set(): with self.config.multi_set():
@ -61,7 +67,8 @@ class Assignment(AssignmentClaim, SynthesizeUI):
quests = self.config.stored.BattlePassTodayQuest.load_quests() quests = self.config.stored.BattlePassTodayQuest.load_quests()
if self.has_new_dispatch: if self.has_new_dispatch:
if KEYWORD_BATTLE_PASS_QUEST.Dispatch_1_assignments in quests: 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') self.config.task_call('BattlePass')
# Check daily # Check daily
quests = self.config.stored.DailyQuest.load_quests() quests = self.config.stored.DailyQuest.load_quests()
@ -93,21 +100,25 @@ class Assignment(AssignmentClaim, SynthesizeUI):
f'User specified assignments: {", ".join([x.name for x in assignments])}') f'User specified assignments: {", ".join([x.name for x in assignments])}')
_, remain, _ = self._limit_status _, remain, _ = self._limit_status
for assignment in assignments: for assignment in assignments:
if assignment in self.dispatched:
continue
if remain <= 0:
yield assignment
continue
logger.hr('Assignment inlist', level=2) logger.hr('Assignment inlist', level=2)
logger.info(f'Check assignment inlist: {assignment}') logger.info(f'Check assignment inlist: {assignment}')
self.goto_entry(assignment) self.goto_entry(assignment)
if self.appear(CLAIM): status = self._check_assignment_status()
if status == AssignmentStatus.CLAIMABLE:
self.claim(assignment, duration, should_redispatch=True) self.claim(assignment, duration, should_redispatch=True)
continue continue
if self.appear(DISPATCHED): if status == AssignmentStatus.DISPATCHED:
self.dispatched[assignment] = datetime.now() + Duration( self.dispatched[assignment] = datetime.now() + \
OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image) self._get_assignment_time()
continue continue
if remain > 0: # General assignments must be dispatchable here
self.dispatch(assignment, duration) self.dispatch(assignment, duration)
remain -= 1 remain -= 1
else:
yield assignment
def _check_all(self): def _check_all(self):
""" """
@ -118,28 +129,31 @@ class Assignment(AssignmentClaim, SynthesizeUI):
Break when a dispatchable assignment is encountered Break when a dispatchable assignment is encountered
""" """
logger.hr('Assignment check all', level=1) logger.hr('Assignment check all', level=1)
_, remain, total = self._limit_status current, remain, _ = self._limit_status
if total == len(self.dispatched): if current == len(self.dispatched):
return remain return remain
for group in self._iter_groups(): for group in self._iter_groups():
self.goto_group(group) self.goto_group(group)
entries = self._iter_entries() insight = False
for _ in range(len(group.entries)): for assignment in self._iter_entries():
assignment = next(entries)
if assignment in self.dispatched: if assignment in self.dispatched:
continue continue
logger.hr('Assignment all', level=2) logger.hr('Assignment all', level=2)
logger.info(f'Check assignment all: {assignment}') logger.info(f'Check assignment all: {assignment}')
self.goto_entry(assignment) self.goto_entry(assignment, insight)
if self.appear(CLAIM): status = self._check_assignment_status()
if status == AssignmentStatus.CLAIMABLE:
self.claim(assignment, None, should_redispatch=False) self.claim(assignment, None, should_redispatch=False)
current -= 1
remain += 1 remain += 1
insight = True # Order of entries change after claiming
continue continue
if self.appear(DISPATCHED): if status == AssignmentStatus.DISPATCHED:
self.dispatched[assignment] = datetime.now() + Duration( self.dispatched[assignment] = datetime.now() + \
OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image) self._get_assignment_time()
if total == len(self.dispatched): if current == len(self.dispatched):
return remain return remain
insight = False # Order of entries does not change here
continue continue
break break
return remain return remain
@ -174,3 +188,29 @@ class Assignment(AssignmentClaim, SynthesizeUI):
remain -= 1 remain -= 1
if remain <= 0: if remain <= 0:
return 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}')
# 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.DISPATCHABLE:
self.dispatch(assignment, None)
remain -= 1
if remain <= 0:
return remain
continue
break
return remain

View File

@ -41,7 +41,7 @@ class AssignmentClaim(AssignmentDispatch):
""" """
Pages: Pages:
in: CLAIM in: CLAIM
out: REDISPATCH out: REPORT
""" """
skip_first_screenshot = True skip_first_screenshot = True
while 1: while 1:
@ -50,7 +50,9 @@ class AssignmentClaim(AssignmentDispatch):
else: else:
self.device.screenshot() self.device.screenshot()
# End # 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') logger.info('Assignment report appears')
break break
# Claim rewards # Claim rewards
@ -63,7 +65,7 @@ class AssignmentClaim(AssignmentDispatch):
should_redispatch (bool): determined by user config and duration in report should_redispatch (bool): determined by user config and duration in report
Pages: Pages:
in: CLOSE_REPORT and REDISPATCH in: REPORT
out: page_assignment out: page_assignment
""" """
click_button = REDISPATCH if should_redispatch else CLOSE_REPORT click_button = REDISPATCH if should_redispatch else CLOSE_REPORT
@ -78,7 +80,8 @@ class AssignmentClaim(AssignmentDispatch):
logger.info('Assignment report is closed') logger.info('Assignment report is closed')
break break
# Close report # Close report
if self.appear_then_click(click_button, interval=2): if self.appear(REPORT, interval=2):
self.device.click(click_button)
continue continue
def _is_duration_expected(self, duration: int) -> bool: def _is_duration_expected(self, duration: int) -> bool:

View File

@ -1,12 +1,37 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from module.base.base import ModuleBase
from module.base.timer import Timer from module.base.timer import Timer
from module.config.stored.classes import now from module.config.stored.classes import now
from module.logger import logger from module.logger import logger
from module.ui.switch import Switch
from tasks.assignment.assets.assets_assignment_dispatch import * from tasks.assignment.assets.assets_assignment_dispatch import *
from tasks.assignment.assets.assets_assignment_ui import DISPATCHED from tasks.assignment.assets.assets_assignment_ui import DISPATCHED
from tasks.assignment.keywords import * from tasks.assignment.keywords import *
from tasks.assignment.ui import AssignmentSwitch, AssignmentUI from tasks.assignment.ui import AssignmentUI
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'
ASSIGNMENT_DURATION_SWITCH = AssignmentSwitch( ASSIGNMENT_DURATION_SWITCH = AssignmentSwitch(
'AssignmentDurationSwitch', 'AssignmentDurationSwitch',
@ -22,21 +47,25 @@ class AssignmentDispatch(AssignmentUI):
dispatched: dict[AssignmentEntry, datetime] = dict() dispatched: dict[AssignmentEntry, datetime] = dict()
has_new_dispatch: bool = False has_new_dispatch: bool = False
def dispatch(self, assignment: AssignmentEntry, duration: int): def dispatch(self, assignment: AssignmentEntry, duration: int | None):
""" """
Dispatch assignment. Dispatch assignment.
Should be called only when limit is checked Should be called only when limit is checked
Args: Args:
assignment (AssignmentEntry): assignment (AssignmentEntry):
duration (int): user specified duration duration (int | None): user specified duration, None for event assignments
Pages: Pages:
in: EMPTY_SLOT in: EMPTY_SLOT
out: DISPATCHED out: DISPATCHED
""" """
self._select_characters() self._select_characters()
self._select_duration(duration) if isinstance(assignment, AssignmentEventEntry):
self._select_support()
duration = self._get_assignment_time().total_seconds() / 3600
else:
self._select_duration(duration)
self._confirm_assignment() self._confirm_assignment()
self._wait_until_assignment_started() self._wait_until_assignment_started()
future = now() + timedelta(hours=duration) future = now() + timedelta(hours=duration)
@ -63,7 +92,8 @@ class AssignmentDispatch(AssignmentUI):
logger.info('Characters are all selected') logger.info('Characters are all selected')
break break
# Ensure character list # Ensure character list
if not self.appear(CHARACTER_LIST): # Search EMPTY_SLOT to load offset
if not self.appear(CHARACTER_LIST) and self.appear(EMPTY_SLOT):
if self.interval_is_reached(CHARACTER_LIST, interval=2): if self.interval_is_reached(CHARACTER_LIST, interval=2):
self.interval_reset(CHARACTER_LIST, interval=2) self.interval_reset(CHARACTER_LIST, interval=2)
self.device.click(EMPTY_SLOT) self.device.click(EMPTY_SLOT)
@ -78,6 +108,30 @@ class AssignmentDispatch(AssignmentUI):
if not self.image_color_count(CHARACTER_2_SELECTED, (240, 240, 240)): if not self.image_color_count(CHARACTER_2_SELECTED, (240, 240, 240)):
self.device.click(CHARACTER_2) 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): def _select_duration(self, duration: int):
if duration not in {4, 8, 12, 20}: if duration not in {4, 8, 12, 20}:
logger.warning( logger.warning(
@ -133,6 +187,6 @@ class AssignmentDispatch(AssignmentUI):
else: else:
self.device.screenshot() self.device.screenshot()
# End # End
if self.appear(ASSIGNMENT_STARTED_CHECK): if self.match_color(ASSIGNMENT_STARTED_CHECK):
logger.info('Assignment started') logger.info('Assignment started')
break break

View File

@ -1,6 +1,8 @@
import tasks.assignment.keywords.entry as KEYWORDS_ASSIGNMENT_ENTRY import tasks.assignment.keywords.entry as KEYWORDS_ASSIGNMENT_ENTRY
import tasks.assignment.keywords.group as KEYWORDS_ASSIGNMENT_GROUP 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_GROUP.Character_Materials.entries = (
KEYWORDS_ASSIGNMENT_ENTRY.Nine_Billion_Names, 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.Legend_of_the_Puppet_Master,
KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity, 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 ( for group in (
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, KEYWORDS_ASSIGNMENT_GROUP.Character_Materials,
KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits, KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits,
KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials, KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials,
KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force,
): ):
for entry in group.entries: for entry in group.entries:
assert entry.group is None assert entry.group is None

View File

@ -15,6 +15,19 @@ class AssignmentGroup(Keyword):
class AssignmentEntry(Keyword): class AssignmentEntry(Keyword):
instances: ClassVar = {} instances: ClassVar = {}
group: AssignmentGroup = None group: AssignmentGroup = None
def __hash__(self) -> int: def __hash__(self) -> int:
return super().__hash__() 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,221 @@
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='斥力ブリッジエラー',
es='Reporte de error del puente de rechazo',
)
Meal_Delivery_Robot_Check_Up = AssignmentEventEntry(
id=2,
name='Meal_Delivery_Robot_Check_Up',
cn='送餐机器人检修',
cht='送餐機器人檢修',
en='Meal-Delivery Robot Check-Up',
jp='配膳ロボット点検修理',
es='Mantenimiento de los robots de reparto de comida',
)
Noise_Complaint = AssignmentEventEntry(
id=3,
name='Noise_Complaint',
cn='噪音投诉问题',
cht='噪音投訴問題',
en='Noise Complaint',
jp='騒音苦情問題',
es='Quejas por ruidos',
)
Interior_Temperature_Modulator = AssignmentEventEntry(
id=4,
name='Interior_Temperature_Modulator',
cn='室内温度调节器',
cht='室內溫度調節器',
en='Interior Temperature Modulator',
jp='室内温度調節器',
es='Regulador de temperatura ambiental',
)
Researcher_Health_Reports = AssignmentEventEntry(
id=5,
name='Researcher_Health_Reports',
cn='科员的体检报告',
cht='組員的體檢報告',
en="Researchers' Health Reports",
jp='スタッフの健康診断報告',
es='Informe médico de los investigadores',
)
Confidential_Investigation = AssignmentEventEntry(
id=6,
name='Confidential_Investigation',
cn='秘密调查行动',
cht='秘密調查行動',
en='Confidential Investigation',
jp='秘密裏の調査',
es='Investigación encubierta',
)
Borrowed_Equipment = AssignmentEventEntry(
id=7,
name='Borrowed_Equipment',
cn='实验器械借用',
cht='實驗器械借用',
en='Borrowed Equipment',
jp='実験機器借用',
es='Préstamo de instrumentos de laboratorio',
)
Booking_System = AssignmentEventEntry(
id=8,
name='Booking_System',
cn='会议室预约系统',
cht='會議室預約系統',
en='Booking System',
jp='会議室予約システム',
es='Sistema de reserva de las salas de reuniones',
)
Non_Digital_Documents = AssignmentEventEntry(
id=9,
name='Non_Digital_Documents',
cn='非电子版文件',
cht='非電子版文件',
en='Non-Digital Documents',
jp='非デジタル版ファイル',
es='Documentos no electrónicos',
)
Drip_Feed_Errors = AssignmentEventEntry(
id=10,
name='Drip_Feed_Errors',
cn='液滴系统报错',
cht='液滴系統錯誤',
en='Drip-Feed Errors',
jp='水やりシステムエラー',
es='Reporte de error del sistema de goteo',
)
Pet_Movement_Route_Planning = AssignmentEventEntry(
id=11,
name='Pet_Movement_Route_Planning',
cn='宠物行动路线规划',
cht='寵物行動路線規劃',
en='Pet Movement Route Planning',
jp='ペットの行動ルート規制',
es='Planificación de las rutas de paseo de mascotas',
)
Food_Improvement_Plan = AssignmentEventEntry(
id=12,
name='Food_Improvement_Plan',
cn='餐饮优化方案',
cht='餐飲改良方案',
en='Food Improvement Plan',
jp='飲食優良化法案',
es='Programa de mejora de comida',
)
Curio_Distribution = AssignmentEventEntry(
id=13,
name='Curio_Distribution',
cn='奇物借用问题',
cht='奇物借用問題',
en='Curio Distribution',
jp='奇物借用問題',
es='Problemas con el préstamo de objetos raros',
)
Super_Urgent_Waiting_Online = AssignmentEventEntry(
id=14,
name='Super_Urgent_Waiting_Online',
cn='来活人很急在线等',
cht='急,線上等',
en='Super Urgent, Waiting Online',
jp='緊急助っ人求むオンラインにて待つ',
es='Muy urgente, esperando en línea',
)
Ventilation_Problem = AssignmentEventEntry(
id=15,
name='Ventilation_Problem',
cn='空气流通问题',
cht='空氣流通問題',
en='Ventilation Problem',
jp='換気問題',
es='Problemas con la circulación del aire',
)
Unstable_Connection = AssignmentEventEntry(
id=16,
name='Unstable_Connection',
cn='连接不稳定问题',
cht='連線不穩定問題',
en='Unstable Connection',
jp='接続不安定問題',
es='Conexión inestable',
)
Chronology_Checks = AssignmentEventEntry(
id=17,
name='Chronology_Checks',
cn='编年史校对',
cht='編年史校對',
en='Chronology Checks',
jp='編年史校正',
es='Corrección de registros',
)
Supply_Chain_Management = AssignmentEventEntry(
id=18,
name='Supply_Chain_Management',
cn='物流供应链管理',
cht='物流供應鏈管理',
en='Supply Chain Management',
jp='物流供給路線管理',
es='Gestión de pedidos',
)
Malicious_Occupation_of_Public_Space = AssignmentEventEntry(
id=19,
name='Malicious_Occupation_of_Public_Space',
cn='公共区域被恶意侵占',
cht='公共區域被惡意侵佔',
en='Malicious Occupation of Public Space',
jp='公共区域の悪意による独占',
es='Invasión de espacios públicos',
)
Uniform_Material = AssignmentEventEntry(
id=20,
name='Uniform_Material',
cn='科室服装面料',
cht='科室服裝材質',
en='Uniform Material',
jp='スタッフ制服の素材',
es='Material del uniforme',
)
Virus_Re_creation_Report = AssignmentEventEntry(
id=21,
name='Virus_Re_creation_Report',
cn='病毒溯源报告',
cht='病毒溯源報告',
en='Virus Re-creation Report',
jp='ウイルス根源報告',
es='Informe de rastreo de virus',
)
Abnormal_Signal = AssignmentEventEntry(
id=22,
name='Abnormal_Signal',
cn='舱段信号异常',
cht='艙段訊號異常',
en='Abnormal Signal',
jp='部分の信号異常',
es='Mala señal en las cabinas',
)
Flexible_Working_Approval = AssignmentEventEntry(
id=23,
name='Flexible_Working_Approval',
cn='轮休审批流程',
cht='輪休審批流程',
en='Flexible Working Approval',
jp='交代休み審査フロー',
es='Aprobación de días de descanso',
)
Lighting_Issue = AssignmentEventEntry(
id=24,
name='Lighting_Issue',
cn='灯光照明问题',
cht='燈光照明問題',
en='Lighting Issue',
jp='照明の色問題',
es='Problemas de iluminación',
)

View File

@ -0,0 +1,14 @@
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='ステーション特派',
es='Comando de la Estación Espacial',
)

View File

@ -1,39 +1,27 @@
import re import re
from collections.abc import Iterator
from datetime import timedelta
from enum import Enum
from functools import cached_property from functools import cached_property
from typing import Iterator
from module.base.base import ModuleBase
from module.base.timer import Timer from module.base.timer import Timer
from module.exception import ScriptError from module.exception import ScriptError
from module.logger import logger from module.logger import logger
from module.ocr.ocr import DigitCounter, Duration, Ocr from module.ocr.ocr import DigitCounter, Duration, Ocr
from tasks.dungeon.ui import DungeonTabSwitch as Switch
from module.ui.draggable_list import DraggableList from module.ui.draggable_list import DraggableList
from module.ui.switch import Switch 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.assets.assets_assignment_ui import *
from tasks.assignment.keywords import * from tasks.assignment.keywords import *
from tasks.base.ui import UI from tasks.base.ui import UI
class AssignmentSwitch(Switch): class AssignmentStatus(Enum):
def __init__(self, name, active_color: tuple[int, int, int], is_selector=True): CLAIMABLE = 0
super().__init__(name, is_selector) DISPATCHED = 1
self.active_color = active_color DISPATCHABLE = 2
LOCKED = 3
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): class AssignmentOcr(Ocr):
@ -50,6 +38,10 @@ class AssignmentOcr(Ocr):
(KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records.name, '阿[未][夏复]记录'), (KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records.name, '阿[未][夏复]记录'),
(KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master.name, '^师传说'), (KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master.name, '^师传说'),
(KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity.name, '[赠]养人类'), (KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity.name, '[赠]养人类'),
],
'en': [
(KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Food_Improvement_Plan.name,
'Food\s*[I]{0}mprovement Plan'),
] ]
} }
@ -63,7 +55,15 @@ class AssignmentOcr(Ocr):
def filter_detected(self, result) -> bool: def filter_detected(self, result) -> bool:
# Drop duration rows # Drop duration rows
res = Duration.timedelta_regex(self.lang).search(result.ocr_text) 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): def after_process(self, result: str):
result = super().after_process(result) result = super().after_process(result)
@ -74,38 +74,57 @@ class AssignmentOcr(Ocr):
if matched is None: if matched is None:
return result return result
keyword_lang = self.lang keyword_lang = self.lang
matched = getattr(KEYWORDS_ASSIGNMENT_ENTRY, matched.lastgroup) for keyword_class in (
KEYWORDS_ASSIGNMENT_ENTRY,
KEYWORDS_ASSIGNMENT_EVENT_ENTRY,
):
matched = getattr(keyword_class, matched.lastgroup, None)
if matched is not None:
break
else:
raise ScriptError(f'No keyword found for {matched.lastgroup}')
matched = getattr(matched, keyword_lang) matched = getattr(matched, keyword_lang)
logger.attr(name=f'{self.name} after_process', logger.attr(name=f'{self.name} after_process',
text=f'{result} -> {matched}') text=f'{result} -> {matched}')
return matched return matched
ASSIGNMENT_TOP_SWITCH = AssignmentSwitch( ASSIGNMENT_GROUP_SWITCH = Switch(
'AssignmentTopSwitch', 'AssignmentGroupSwitch',
(240, 240, 240) is_selector=True
) )
ASSIGNMENT_TOP_SWITCH.add_state( 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_GROUP.Character_Materials, KEYWORDS_ASSIGNMENT_GROUP.Character_Materials,
check_button=CHARACTER_MATERIALS check_button=CHARACTER_MATERIALS_CHECK,
click_button=CHARACTER_MATERIALS_CLICK
) )
ASSIGNMENT_TOP_SWITCH.add_state( ASSIGNMENT_GROUP_SWITCH.add_state(
KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits, KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits,
check_button=EXP_MATERIALS_CREDITS check_button=EXP_MATERIALS_CREDITS_CHECK,
click_button=EXP_MATERIALS_CREDITS_CLICK
) )
ASSIGNMENT_TOP_SWITCH.add_state( ASSIGNMENT_GROUP_SWITCH.add_state(
KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials, KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials,
check_button=SYNTHESIS_MATERIALS check_button=SYNTHESIS_MATERIALS_CHECK,
click_button=SYNTHESIS_MATERIALS_CLICK
) )
ASSIGNMENT_ENTRY_LIST = DraggableList( ASSIGNMENT_ENTRY_LIST = DraggableList(
'AssignmentEntryList', 'AssignmentEntryList',
keyword_class=AssignmentEntry, keyword_class=[AssignmentEntry, AssignmentEventEntry],
ocr_class=AssignmentOcr, ocr_class=AssignmentOcr,
search_button=OCR_ASSIGNMENT_LIST, search_button=OCR_ASSIGNMENT_ENTRY_LIST,
check_row_order=False, check_row_order=False,
active_color=(40, 40, 40) active_color=(40, 40, 40)
) )
ASSIGNMENT_ENTRY_LIST.known_rows = [
kw for kc in ASSIGNMENT_ENTRY_LIST.keyword_class
for kw in kc.instances.values()
]
class AssignmentUI(UI): class AssignmentUI(UI):
@ -119,14 +138,17 @@ class AssignmentUI(UI):
self.device.screenshot() self.device.screenshot()
self.goto_group(KEYWORDS_ASSIGNMENT_GROUP.Character_Materials) self.goto_group(KEYWORDS_ASSIGNMENT_GROUP.Character_Materials)
""" """
if ASSIGNMENT_GROUP_SWITCH.get(self) == group:
return
logger.hr('Assignment group goto', level=3) logger.hr('Assignment group goto', level=3)
if ASSIGNMENT_TOP_SWITCH.set(group, main=self): if ASSIGNMENT_GROUP_SWITCH.set(group, self):
self._wait_until_entry_loaded() self._wait_until_entry_loaded()
def goto_entry(self, entry: AssignmentEntry): def goto_entry(self, entry: AssignmentEntry, insight: bool = True):
""" """
Args: Args:
entry (AssignmentEntry): entry (AssignmentEntry):
insight (bool): skip ocr to save time if insight is False
Examples: Examples:
self = AssignmentUI('src') self = AssignmentUI('src')
@ -143,7 +165,24 @@ class AssignmentUI(UI):
raise ScriptError(err_msg) raise ScriptError(err_msg)
else: else:
self.goto_group(entry.group) self.goto_group(entry.group)
ASSIGNMENT_ENTRY_LIST.select_row(entry, self) ASSIGNMENT_ENTRY_LIST.select_row(entry, self, insight=insight)
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(GROUP_SEARCH, (40, 40, 40), count=25000) and \
self.image_color_count(GROUP_SEARCH, (240, 240, 240), count=8000):
logger.info('Group loaded')
break
def _wait_until_entry_loaded(self): def _wait_until_entry_loaded(self):
skip_first_screenshot = True skip_first_screenshot = True
@ -175,14 +214,51 @@ class AssignmentUI(UI):
self.config.stored.Assignment.set(0, 0) self.config.stored.Assignment.set(0, 0)
return current, remain, total return current, remain, total
def _check_assignment_status(self) -> AssignmentStatus:
skip_first_screenshot = True
timeout = Timer(2, count=3).start()
ret = AssignmentStatus.LOCKED
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if timeout.reached():
logger.info(
'Check assignment status timeout, assume LOCKED'
)
break
if self.appear(CLAIM):
ret = AssignmentStatus.CLAIMABLE
break
if self.appear(DISPATCHED):
ret = AssignmentStatus.DISPATCHED
break
if self.appear(EMPTY_SLOT):
ret = AssignmentStatus.DISPATCHABLE
break
logger.attr('AssignmentStatus', ret.name)
return ret
def _get_assignment_time(self) -> timedelta:
return Duration(OCR_ASSIGNMENT_TIME).ocr_single_line(self.device.image)
def _iter_groups(self) -> Iterator[AssignmentGroup]: def _iter_groups(self) -> Iterator[AssignmentGroup]:
for state in ASSIGNMENT_TOP_SWITCH.state_list: self._wait_until_group_loaded()
yield state['state'] for state in ASSIGNMENT_GROUP_SWITCH.state_list:
check = state['check_button']
click = state['click_button']
if self.appear(check) or self.appear(click):
yield state['state']
def _iter_entries(self) -> Iterator[AssignmentEntry]: def _iter_entries(self) -> Iterator[AssignmentEntry]:
""" """
Iterate entries from top to bottom Iterate entries from top to bottom
""" """
ASSIGNMENT_ENTRY_LIST.load_rows(main=self) ASSIGNMENT_ENTRY_LIST.load_rows(main=self)
for button in ASSIGNMENT_ENTRY_LIST.cur_buttons: # Freeze ocr results here
yield button.matched_keyword yield from [
button.matched_keyword
for button in ASSIGNMENT_ENTRY_LIST.cur_buttons
]

View File

@ -61,6 +61,8 @@ class Login(UI):
# Additional # Additional
if self.handle_popup_single(): if self.handle_popup_single():
continue continue
if self.handle_popup_confirm():
continue
if self.ui_additional(): if self.ui_additional():
continue continue