feat: Event assignments (All About Boothill)

This commit is contained in:
Zebartin 2024-05-19 21:50:00 +08:00 committed by LmeSzinc
parent ab32ad579c
commit 99a6ab2a11
18 changed files with 170 additions and 74 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 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: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -126,6 +126,7 @@ class GenerateAssignmentEventGroup(GenerateKeyword):
def iter_keywords(self) -> Iterable[dict]:
yield dict(text_id=self.find_keyword('空间站特派', lang='cn')[0])
yield dict(text_id=self.find_keyword('关于波提欧的一切…', lang='cn')[0])
class GenerateAssignmentEventEntry(GenerateKeyword):

View File

@ -907,7 +907,7 @@
"20": "20 Hours"
},
"Event": {
"name": "Complete Event Assignments First",
"name": "Complete Event Assignments",
"help": ""
},
"Assignment": {

View File

@ -907,7 +907,7 @@
"20": "20小时"
},
"Event": {
"name": "有活动时优先做活动委托",
"name": "有活动时参加活动委托",
"help": ""
},
"Assignment": {

View File

@ -907,7 +907,7 @@
"20": "20小時"
},
"Event": {
"name": "有活動時優先做活動委託",
"name": "有活動時參加活動委託",
"help": ""
},
"Assignment": {

View File

@ -1,12 +1,11 @@
from tasks.assignment.assets.assets_assignment_ui import (
ALL_ABOUT_BOOTHILL_CHECK, ALL_ABOUT_BOOTHILL_CLICK,
CHARACTER_MATERIALS_CHECK, CHARACTER_MATERIALS_CLICK,
EXP_MATERIALS_CREDITS_CHECK, EXP_MATERIALS_CREDITS_CLICK, GROUP_SEARCH,
SPACE_STATION_TASK_FORCE_CHECK, SPACE_STATION_TASK_FORCE_CLICK,
SYNTHESIS_MATERIALS_CHECK, SYNTHESIS_MATERIALS_CLICK
)
SYNTHESIS_MATERIALS_CHECK, SYNTHESIS_MATERIALS_CLICK)
for group_button_wrapper in (
SPACE_STATION_TASK_FORCE_CHECK, SPACE_STATION_TASK_FORCE_CLICK,
ALL_ABOUT_BOOTHILL_CHECK, ALL_ABOUT_BOOTHILL_CLICK,
CHARACTER_MATERIALS_CHECK, CHARACTER_MATERIALS_CLICK,
EXP_MATERIALS_CREDITS_CHECK, EXP_MATERIALS_CREDITS_CLICK,
SYNTHESIS_MATERIALS_CHECK, SYNTHESIS_MATERIALS_CLICK,

View File

@ -5,30 +5,57 @@ from module.base.button import Button, ButtonWrapper
ASSIGNMENT_START = ButtonWrapper(
name='ASSIGNMENT_START',
cn=Button(
file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.png',
area=(563, 341, 716, 376),
search=(552, 299, 725, 412),
color=(103, 92, 72),
button=(563, 341, 716, 376),
),
en=Button(
file='./assets/en/assignment/dispatch/ASSIGNMENT_START.png',
area=(693, 343, 831, 374),
search=(669, 297, 831, 416),
color=(95, 86, 67),
button=(693, 343, 831, 374),
),
cn=[
Button(
file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.png',
area=(563, 341, 716, 376),
search=(552, 299, 725, 412),
color=(103, 92, 72),
button=(563, 341, 716, 376),
),
Button(
file='./assets/cn/assignment/dispatch/ASSIGNMENT_START.2.png',
area=(579, 373, 702, 405),
search=(552, 299, 725, 412),
color=(84, 75, 59),
button=(579, 373, 702, 405),
),
],
en=[
Button(
file='./assets/en/assignment/dispatch/ASSIGNMENT_START.png',
area=(693, 343, 831, 374),
search=(669, 297, 831, 416),
color=(95, 86, 67),
button=(693, 343, 831, 374),
),
Button(
file='./assets/en/assignment/dispatch/ASSIGNMENT_START.2.png',
area=(676, 376, 787, 404),
search=(669, 297, 831, 416),
color=(80, 71, 56),
button=(676, 376, 787, 404),
),
],
)
ASSIGNMENT_STARTED_CHECK = ButtonWrapper(
name='ASSIGNMENT_STARTED_CHECK',
share=Button(
file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png',
area=(542, 412, 1156, 422),
search=(522, 392, 1176, 442),
color=(232, 230, 226),
button=(542, 412, 1156, 422),
),
share=[
Button(
file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.png',
area=(542, 412, 1156, 422),
search=(522, 392, 1176, 442),
color=(232, 230, 226),
button=(542, 412, 1156, 422),
),
Button(
file='./assets/share/assignment/dispatch/ASSIGNMENT_STARTED_CHECK.2.png',
area=(542, 412, 1156, 422),
search=(522, 392, 1176, 442),
color=(254, 244, 221),
button=(542, 412, 1156, 422),
),
],
)
CHARACTER_1 = ButtonWrapper(
name='CHARACTER_1',

View File

@ -3,6 +3,40 @@ from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
ALL_ABOUT_BOOTHILL_CHECK = ButtonWrapper(
name='ALL_ABOUT_BOOTHILL_CHECK',
cn=Button(
file='./assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png',
area=(153, 98, 226, 118),
search=(133, 78, 246, 138),
color=(186, 180, 164),
button=(153, 98, 226, 118),
),
en=Button(
file='./assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CHECK.png',
area=(154, 89, 240, 129),
search=(134, 69, 260, 149),
color=(207, 201, 183),
button=(154, 89, 240, 129),
),
)
ALL_ABOUT_BOOTHILL_CLICK = ButtonWrapper(
name='ALL_ABOUT_BOOTHILL_CLICK',
cn=Button(
file='./assets/cn/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png',
area=(153, 98, 227, 119),
search=(133, 78, 247, 139),
color=(80, 79, 77),
button=(153, 98, 227, 119),
),
en=Button(
file='./assets/en/assignment/ui/ALL_ABOUT_BOOTHILL_CLICK.png',
area=(154, 90, 241, 128),
search=(134, 70, 261, 148),
color=(59, 57, 56),
button=(154, 90, 241, 128),
),
)
CHARACTER_MATERIALS_CHECK = ButtonWrapper(
name='CHARACTER_MATERIALS_CHECK',
cn=Button(

View File

@ -2,7 +2,9 @@ from datetime import datetime
from module.logger import logger
from tasks.assignment.claim import AssignmentClaim
from tasks.assignment.keywords import (AssignmentEntry, AssignmentEventGroup, KEYWORDS_ASSIGNMENT_GROUP)
from tasks.assignment.keywords import (KEYWORDS_ASSIGNMENT_GROUP,
AssignmentEntry, AssignmentEventEntry,
AssignmentEventGroup)
from tasks.assignment.ui import AssignmentStatus
from tasks.base.page import page_assignment, page_menu
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
@ -10,7 +12,7 @@ from tasks.daily.synthesize import SynthesizeUI
class Assignment(AssignmentClaim, SynthesizeUI):
def run(self, assignments: list[AssignmentEntry] = None, duration: int = None, event_first: bool = None):
def run(self, assignments: list[AssignmentEntry] = None, duration: int = None, join_event: bool = None):
self.config.update_battle_pass_quests()
self.config.update_daily_quests()
@ -26,25 +28,24 @@ class Assignment(AssignmentClaim, SynthesizeUI):
'There are duplicate assignments in config, check it out')
if duration is None:
duration = self.config.Assignment_Duration
if event_first is None:
event_first = self.config.Assignment_Event
if join_event is None:
join_event = self.config.Assignment_Event
self.dispatched = dict()
self.has_new_dispatch = False
self.ensure_scroll_top(page_menu)
self.ui_ensure(page_assignment)
self._wait_until_group_loaded()
event_ongoing = next((
g for g in self._iter_groups()
if isinstance(g, AssignmentEventGroup)
), None)
if event_first and event_ongoing is not None:
undispatched = assignments
remain = self._check_all()
remain = self._dispatch_event(remain)
else:
# Iterate in user-specified order, return undispatched ones
undispatched = list(self._check_inlist(assignments, duration))
remain = self._check_all()
if join_event and event_ongoing is not None:
if self._check_event():
self._check_event()
# Iterate in user-specified order, return undispatched ones
undispatched = list(self._check_inlist(assignments, duration))
remain = self._check_all()
undispatched = [x for x in undispatched if x not in self.dispatched]
# There are unchecked assignments
if remain > 0:
@ -89,13 +90,15 @@ class Assignment(AssignmentClaim, SynthesizeUI):
logger.hr('Assignment check inlist', level=1)
logger.info(
f'User specified assignments: {", ".join([x.name for x in assignments])}')
_, remain, _ = self._limit_status
remain = None
for assignment in assignments:
if assignment in self.dispatched:
continue
logger.hr('Assignment inlist', level=2)
logger.info(f'Check assignment inlist: {assignment}')
self.goto_entry(assignment)
if remain is None:
_, remain, _ = self._limit_status
status = self._check_assignment_status()
if status == AssignmentStatus.CLAIMABLE:
self.claim(assignment, duration, should_redispatch=True)
@ -121,10 +124,16 @@ class Assignment(AssignmentClaim, SynthesizeUI):
"""
logger.hr('Assignment check all', level=1)
current, remain, _ = self._limit_status
len_dispatched = len([
x for x in self.dispatched.keys()
if not isinstance(x, AssignmentEventEntry)
])
# current = #Claimable + #Dispatched
if current == len(self.dispatched):
if current == len_dispatched:
return remain
for group in self._iter_groups():
if isinstance(group, AssignmentEventGroup):
continue
self.goto_group(group)
insight = False
for assignment in self._iter_entries():
@ -139,14 +148,15 @@ class Assignment(AssignmentClaim, SynthesizeUI):
current -= 1
remain += 1
insight = True # Order of entries change after claiming
if current == len(self.dispatched):
if current == len_dispatched:
return remain
continue
if status == AssignmentStatus.DISPATCHED:
self.dispatched[assignment] = datetime.now() + \
self._get_assignment_time()
len_dispatched += 1
insight = False # Order of entries does not change here
if current == len(self.dispatched):
if current == len_dispatched:
return remain
continue
break
@ -183,10 +193,9 @@ class Assignment(AssignmentClaim, SynthesizeUI):
if remain <= 0:
return
def _dispatch_event(self, remain: int):
if remain <= 0:
return remain
logger.hr('Assignment dispatch event', level=1)
def _check_event(self):
logger.hr('Assignment check event', level=1)
claimed = False
for group in self._iter_groups():
if not isinstance(group, AssignmentEventGroup):
continue
@ -199,12 +208,14 @@ class Assignment(AssignmentClaim, SynthesizeUI):
# Order of entries does not change during iteration
self.goto_entry(assignment, insight=False)
status = self._check_assignment_status()
# Should only be dispatchable or locked after _check_all
if status == AssignmentStatus.LOCKED:
continue
if status == AssignmentStatus.CLAIMABLE:
self.claim(assignment, None, should_redispatch=False)
claimed = True
if status == AssignmentStatus.DISPATCHABLE:
self.dispatch(assignment, None)
remain -= 1
if remain <= 0:
return remain
continue
break
return remain
if status == AssignmentStatus.DISPATCHED:
self.dispatched[assignment] = datetime.now() + \
self._get_assignment_time()
return claimed

View File

@ -30,7 +30,7 @@ KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials.entries = (
KEYWORDS_ASSIGNMENT_ENTRY.Fragments_of_Illusory_Dreams,
KEYWORDS_ASSIGNMENT_ENTRY.Scalpel_and_Screwdriver,
)
KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force.entries = (
KEYWORDS_ASSIGNMENT_EVENT_GROUP.All_About_Boothill.entries = (
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Activate_Genetic_Samples,
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Reproduce_Experimental_Data,
KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Burned_Warehouse,
@ -60,7 +60,7 @@ for group in (
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials,
KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits,
KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials,
KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force,
KEYWORDS_ASSIGNMENT_EVENT_GROUP.All_About_Boothill,
):
for entry in group.entries:
assert entry.group is None

View File

@ -12,3 +12,12 @@ Space_Station_Task_Force = AssignmentEventGroup(
jp='ステーション特派',
es='Comando de la Estación Espacial',
)
All_About_Boothill = AssignmentEventGroup(
id=2,
name='All_About_Boothill',
cn='关于波提欧的一切…',
cht='關於波提歐的一切……',
en='All About Boothill...',
jp='ブートヒルに関するすべて…',
es='Todo sobre Boothill...',
)

View File

@ -8,13 +8,14 @@ from module.base.timer import Timer
from module.exception import ScriptError
from module.logger import logger
from module.ocr.ocr import DigitCounter, Duration, Ocr
from tasks.dungeon.ui import DungeonTabSwitch as Switch
from module.ui.draggable_list import DraggableList
from tasks.assignment.assets.assets_assignment_claim import CLAIM
from tasks.assignment.assets.assets_assignment_dispatch import EMPTY_SLOT
from tasks.assignment.assets.assets_assignment_ui import *
from tasks.assignment.keywords import *
from tasks.base.assets.assets_base_page import ASSIGNMENT_CHECK
from tasks.base.ui import UI
from tasks.dungeon.ui import DungeonTabSwitch as Switch
class AssignmentStatus(Enum):
@ -31,17 +32,19 @@ class AssignmentOcr(Ocr):
OCR_REPLACE = {
'cn': [
(KEYWORDS_ASSIGNMENT_ENTRY.Winter_Soldiers.name, '[黑]冬的战士们'),
(KEYWORDS_ASSIGNMENT_ENTRY.Born_to_Obey.name, '[牛]而服从'),
(KEYWORDS_ASSIGNMENT_ENTRY.Root_Out_the_Turpitude.name,
(KEYWORDS_ASSIGNMENT_ENTRY.Winter_Soldiers, '[黑]冬的战士们'),
(KEYWORDS_ASSIGNMENT_ENTRY.Born_to_Obey, '[牛]而服从'),
(KEYWORDS_ASSIGNMENT_ENTRY.Root_Out_the_Turpitude,
'根除恶[擎薯尊掌鞋]?'),
(KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records.name, '阿[未][夏复]记录'),
(KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master.name, '^师传说'),
(KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity.name, '[赠]养人类'),
(KEYWORDS_ASSIGNMENT_ENTRY.Akashic_Records, '阿[未][夏复]记录'),
(KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master, '^师传说'),
(KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity, '[赠]养人类'),
(KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Car_Thief, '.*的偷车贼.*'),
],
'en': [
# (KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Food_Improvement_Plan.name,
# 'Food\s*[I]{0}mprovement Plan'),
(KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Car_Thief, '.*Car Thief.*'),
]
}
@ -50,7 +53,10 @@ class AssignmentOcr(Ocr):
rules = AssignmentOcr.OCR_REPLACE.get(self.lang)
if rules is None:
return None
return re.compile('|'.join('(?P<%s>%s)' % pair for pair in rules))
return re.compile('|'.join(
f'(?P<{kw.name}>{pat})'
for kw, pat in rules
))
def filter_detected(self, result) -> bool:
# Drop duration rows
@ -78,17 +84,17 @@ class AssignmentOcr(Ocr):
matched = self.ocr_regex.fullmatch(result)
if matched is None:
return result
keyword_lang = self.lang
for keyword_class in (
KEYWORDS_ASSIGNMENT_ENTRY,
KEYWORDS_ASSIGNMENT_EVENT_ENTRY,
):
matched = getattr(keyword_class, matched.lastgroup, None)
if matched is not None:
kw = getattr(keyword_class, matched.lastgroup, None)
if kw is not None:
matched = kw
break
else:
raise ScriptError(f'No keyword found for {matched.lastgroup}')
matched = getattr(matched, keyword_lang)
matched = getattr(matched, self.lang)
logger.attr(name=f'{self.name} after_process',
text=f'{result} -> {matched}')
return matched
@ -98,10 +104,15 @@ ASSIGNMENT_GROUP_SWITCH = Switch(
'AssignmentGroupSwitch',
is_selector=True
)
# ASSIGNMENT_GROUP_SWITCH.add_state(
# KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force,
# check_button=SPACE_STATION_TASK_FORCE_CHECK,
# click_button=SPACE_STATION_TASK_FORCE_CLICK
# )
ASSIGNMENT_GROUP_SWITCH.add_state(
KEYWORDS_ASSIGNMENT_EVENT_GROUP.Space_Station_Task_Force,
check_button=SPACE_STATION_TASK_FORCE_CHECK,
click_button=SPACE_STATION_TASK_FORCE_CLICK
KEYWORDS_ASSIGNMENT_EVENT_GROUP.All_About_Boothill,
check_button=ALL_ABOUT_BOOTHILL_CHECK,
click_button=ALL_ABOUT_BOOTHILL_CLICK
)
ASSIGNMENT_GROUP_SWITCH.add_state(
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials,
@ -174,7 +185,7 @@ class AssignmentUI(UI):
def _wait_until_group_loaded(self):
skip_first_screenshot = True
timeout = Timer(2, count=3).start()
timeout = Timer(3, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
@ -201,13 +212,17 @@ class AssignmentUI(UI):
if timeout.reached():
logger.warning('Wait entry loaded timeout')
break
if self.image_color_count(ENTRY_LOADED, (35, 35, 35), count=800):
if self.appear(ASSIGNMENT_CHECK) and \
self.image_color_count(ENTRY_LOADED, (35, 35, 35), count=800):
logger.info('Entry loaded')
break
@property
def _limit_status(self) -> tuple[int, int, int]:
self.device.screenshot()
if isinstance(ASSIGNMENT_GROUP_SWITCH.get(self), AssignmentEventGroup):
ASSIGNMENT_GROUP_SWITCH.set(
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials, self)
current, remain, total = DigitCounter(
OCR_ASSIGNMENT_LIMIT).ocr_single_line(self.device.image)
if total and current <= total: