StarRailCopilot/tasks/assignment/assignment.py

241 lines
10 KiB
Python
Raw Normal View History

from datetime import datetime, timedelta
2023-06-19 00:39:41 +00:00
from module.config.stored.classes import now
from module.config.utils import get_server_next_update
2023-06-19 00:39:41 +00:00
from module.logger import logger
from tasks.assignment.claim import AssignmentClaim
from tasks.assignment.keywords import (KEYWORDS_ASSIGNMENT_GROUP,
AssignmentEntry, AssignmentEventEntry,
AssignmentEventGroup)
from tasks.assignment.ui import ASSIGNMENT_ENTRY_LIST, AssignmentStatus
2023-06-19 17:00:34 +00:00
from tasks.base.page import page_assignment, page_menu
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
2023-06-19 17:00:34 +00:00
from tasks.daily.synthesize import SynthesizeUI
2023-06-19 00:39:41 +00:00
2023-06-19 17:00:34 +00:00
class Assignment(AssignmentClaim, SynthesizeUI):
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()
2023-06-19 00:39:41 +00:00
if assignments is None:
assignments = (
getattr(self.config, f'Assignment_Name_{i + 1}', None) for i in range(4))
# remove duplicate while keeping order
assignments = list(dict.fromkeys(
x for x in assignments if x is not None))
assignments = [AssignmentEntry.find(x) for x in assignments]
if len(assignments) < 4:
logger.warning(
'There are duplicate assignments in config, check it out')
2023-06-19 00:39:41 +00:00
if duration is None:
duration = self.config.Assignment_Duration
if join_event is None:
join_event = self.config.Assignment_Event
2023-06-19 00:39:41 +00:00
self.dispatched = dict()
self.has_new_dispatch = False
ASSIGNMENT_ENTRY_LIST.cur_buttons = []
2023-06-19 17:00:34 +00:00
self.ensure_scroll_top(page_menu)
2023-06-19 00:39:41 +00:00
self.ui_ensure(page_assignment)
2023-09-26 07:06:43 +00:00
event_ongoing = next((
g for g in self._iter_groups()
if isinstance(g, AssignmentEventGroup)
), None)
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]
2023-06-19 00:39:41 +00:00
# There are unchecked assignments
2023-06-19 17:00:34 +00:00
if remain > 0:
2023-06-19 00:39:41 +00:00
for assignment in undispatched[:remain]:
self.goto_entry(assignment)
2023-06-19 17:00:34 +00:00
self.dispatch(assignment, duration)
2023-06-19 00:39:41 +00:00
if remain < len(undispatched):
2023-06-19 17:00:34 +00:00
logger.warning('The following assignments can not be dispatched due to limit: '
f'{", ".join([x.name for x in undispatched[remain:]])}')
elif remain > len(undispatched):
self._dispatch_remain(duration, remain - len(undispatched))
2023-09-27 05:53:15 +00:00
# Refresh dashboard before return
_ = self._limit_status
2023-06-19 00:39:41 +00:00
# Scheduler
logger.attr('has_new_dispatch', self.has_new_dispatch)
with self.config.multi_set():
# Check daily
2023-12-27 17:07:33 +00:00
quests = self.config.stored.DailyQuest.load_quests()
2023-12-27 16:43:20 +00:00
if KEYWORDS_DAILY_QUEST.Dispatch_1_assignments in quests:
logger.info('Achieved daily quest Dispatch_1_assignments')
self.config.task_call('DailyQuest')
# Delay self
2023-09-15 17:41:42 +00:00
if len(self.dispatched):
delay = min(self.dispatched.values())
logger.info(f'Delay assignment check to {str(delay)}')
self.config.task_delay(target=delay)
# Align server update
update = get_server_next_update(self.config.Scheduler_ServerUpdate)
if update - delay < timedelta(hours=4):
logger.info('Approaching next day, delay to server update instead')
self.config.task_delay(target=update)
2023-09-15 17:41:42 +00:00
else:
# ValueError: min() arg is an empty sequence
logger.error('Empty dispatched list, delay 2 hours instead')
self.config.task_delay(minute=120)
# Check future daily
if now() > get_server_next_update(self.config.Scheduler_ServerUpdate) - timedelta(minutes=110) \
and KEYWORDS_DAILY_QUEST.Dispatch_1_assignments in quests:
logger.error(
"Assigment is scheduled tomorrow but today's assignment daily haven't been finished yet")
self.config.task_call('DailyQuest')
2023-06-19 00:39:41 +00:00
def _check_inlist(self, assignments: list[AssignmentEntry], duration: int):
"""
Dispatch assignments according to user config
Args:
assignments (list[AssignmentEntry]): user specified assignments
duration (int): user specified duration
"""
if not assignments:
return
2023-09-15 17:52:33 +00:00
logger.hr('Assignment check inlist', level=1)
2023-06-19 00:39:41 +00:00
logger.info(
f'User specified assignments: {", ".join([x.name for x in assignments])}')
remain = None
2024-05-20 10:07:39 +00:00
insight = False
2023-06-19 00:39:41 +00:00
for assignment in assignments:
2023-09-26 07:06:43 +00:00
if assignment in self.dispatched:
continue
2023-09-15 17:52:33 +00:00
logger.hr('Assignment inlist', level=2)
logger.info(f'Check assignment inlist: {assignment}')
2024-05-20 10:07:39 +00:00
self.goto_entry(assignment, insight=insight)
insight = True
if remain is None:
_, remain, _ = self._limit_status
status = self._check_assignment_status()
if status == AssignmentStatus.CLAIMABLE:
2023-06-19 00:39:41 +00:00
self.claim(assignment, duration, should_redispatch=True)
continue
if status == AssignmentStatus.DISPATCHED:
2023-09-26 07:06:43 +00:00
self.dispatched[assignment] = datetime.now() + \
self._get_assignment_time()
2024-05-20 10:07:39 +00:00
insight = False
2023-06-19 00:39:41 +00:00
continue
# General assignments must be dispatchable here
if remain <= 0:
yield assignment
continue
self.dispatch(assignment, duration)
remain -= 1
2023-06-19 00:39:41 +00:00
def _check_all(self):
"""
States of assignments from top to bottom are in following order:
1. Claimable
2. Dispatched
3. Dispatchable
Break when a dispatchable assignment is encountered
"""
2023-09-15 17:52:33 +00:00
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_dispatched:
2023-06-19 17:00:34 +00:00
return remain
2023-06-19 00:39:41 +00:00
for group in self._iter_groups():
if isinstance(group, AssignmentEventGroup):
continue
2023-06-19 00:39:41 +00:00
self.goto_group(group)
2023-09-26 15:11:45 +00:00
insight = False
2023-09-26 07:06:43 +00:00
for assignment in self._iter_entries():
2023-06-19 00:39:41 +00:00
if assignment in self.dispatched:
continue
2023-09-15 17:52:33 +00:00
logger.hr('Assignment all', level=2)
logger.info(f'Check assignment all: {assignment}')
self.goto_entry(assignment, insight=insight)
status = self._check_assignment_status()
if status == AssignmentStatus.CLAIMABLE:
2023-06-19 00:39:41 +00:00
self.claim(assignment, None, should_redispatch=False)
current -= 1
2023-06-19 17:00:34 +00:00
remain += 1
2023-09-26 15:11:45 +00:00
insight = True # Order of entries change after claiming
if current == len_dispatched:
return remain
2023-06-19 00:39:41 +00:00
continue
if status == AssignmentStatus.DISPATCHED:
2023-09-26 07:06:43 +00:00
self.dispatched[assignment] = datetime.now() + \
self._get_assignment_time()
len_dispatched += 1
insight = False # Order of entries does not change here
if current == len_dispatched:
return remain
2023-06-19 00:39:41 +00:00
continue
break
2023-06-19 17:00:34 +00:00
return remain
2023-06-19 00:39:41 +00:00
def _dispatch_remain(self, duration: int, remain: int):
"""
Dispatch assignments according to preset priority
Args:
duration (int): user specified duration
remain (int):
The number of remaining assignments after
processing the ones specified by user
"""
if remain <= 0:
return
logger.hr('Assignment dispatch remain', level=2)
logger.warning(f'{remain} remain')
logger.info(
'Dispatch remaining assignments according to preset priority')
group_priority = (
KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits,
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials,
KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials
)
for group in group_priority:
for assignment in group.entries:
if assignment in self.dispatched:
continue
self.goto_entry(assignment)
2023-06-19 17:00:34 +00:00
self.dispatch(assignment, duration)
2023-06-19 00:39:41 +00:00
remain -= 1
if remain <= 0:
return
2023-09-26 07:06:43 +00:00
def _check_event(self):
logger.hr('Assignment check event', level=1)
claimed = False
2023-09-26 07:06:43 +00:00
for group in self._iter_groups():
if not isinstance(group, AssignmentEventGroup):
continue
self.goto_group(group)
insight = False
2023-09-26 07:06:43 +00:00
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}')
2024-05-20 01:22:45 +00:00
# Order of entries changes if claimed
self.goto_entry(assignment, insight=insight)
insight = False
status = self._check_assignment_status()
if status == AssignmentStatus.LOCKED:
continue
elif status == AssignmentStatus.CLAIMABLE:
self.claim(assignment, None, should_redispatch=False)
claimed = True
insight = True
elif status == AssignmentStatus.DISPATCHABLE:
2023-09-26 07:06:43 +00:00
self.dispatch(assignment, None)
elif status == AssignmentStatus.DISPATCHED:
self.dispatched[assignment] = datetime.now() + \
self._get_assignment_time()
return claimed