Add: Task Ornament

This commit is contained in:
LmeSzinc 2024-07-02 02:41:12 +08:00
parent 71e01d9bd8
commit 4afe0bf908
7 changed files with 184 additions and 52 deletions

View File

@ -10,7 +10,7 @@ class ManualConfig:
SCHEDULER_PRIORITY = """ SCHEDULER_PRIORITY = """
Restart Restart
> Weekly > Dungeon > Assignment > Weekly > Ornament > Dungeon > Assignment
> BattlePass > DailyQuest > BattlePass > DailyQuest
> Freebies > DataUpdate > Freebies > DataUpdate
> Rogue > Rogue

4
src.py
View File

@ -58,6 +58,10 @@ class StarRailCopilot(AzurLaneAutoScript):
from tasks.rogue.rogue import Rogue from tasks.rogue.rogue import Rogue
Rogue(config=self.config, device=self.device).run() Rogue(config=self.config, device=self.device).run()
def ornament(self):
from tasks.ornament.ornament import Ornament
Ornament(config=self.config, device=self.device).run()
def benchmark(self): def benchmark(self):
from module.daemon.benchmark import run_benchmark from module.daemon.benchmark import run_benchmark
run_benchmark(config=self.config) run_benchmark(config=self.config)

View File

@ -15,6 +15,7 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
achieved_daily_quest = False achieved_daily_quest = False
achieved_weekly_quest = False achieved_weekly_quest = False
running_double = False running_double = False
support_once = True
daily_quests = [] daily_quests = []
weekly_quests = [] weekly_quests = []
@ -76,14 +77,16 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
wave_limit = relic wave_limit = relic
if relic == 0: if relic == 0:
return 0 return 0
if dungeon.is_Ornament_Extraction and self.running_double and \ # No need, already checked in Survival_Index
self.config.stored.DungeonDouble.rogue > 0: # if dungeon.is_Ornament_Extraction and self.running_double and \
rogue = self.get_double_event_remain_at_combat() # self.config.stored.DungeonDouble.rogue > 0:
if rogue is not None and rogue < self.config.stored.DungeonDouble.rogue: # rogue = self.get_double_event_remain_at_combat()
self.config.stored.DungeonDouble.rogue = rogue # if rogue is not None and rogue < self.config.stored.DungeonDouble.rogue:
wave_limit = rogue # self.config.stored.DungeonDouble.rogue = rogue
if rogue == 0: # wave_limit = rogue
return 0 # if rogue == 0:
# return 0
# Combat # Combat
self.dungeon = dungeon self.dungeon = dungeon
count = self.combat(team=team, wave_limit=wave_limit, support_character=support_character) count = self.combat(team=team, wave_limit=wave_limit, support_character=support_character)
@ -177,7 +180,7 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
out: page_main out: page_main
""" """
require = self.require_compulsory_support() require = self.require_compulsory_support()
if require: if require and self.support_once:
logger.info('Run once with support') logger.info('Run once with support')
count = self._dungeon_run(dungeon=dungeon, team=team, wave_limit=1, count = self._dungeon_run(dungeon=dungeon, team=team, wave_limit=1,
support_character=self.config.DungeonSupport_Character) support_character=self.config.DungeonSupport_Character)
@ -192,22 +195,22 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
return count return count
elif require and not self.support_once:
# Run with support all the way
return self._dungeon_run(dungeon=dungeon, team=team, wave_limit=1,
support_character=self.config.DungeonSupport_Character)
else: else:
# Normal run # Normal run
return self._dungeon_run(dungeon=dungeon, team=team, wave_limit=wave_limit, return self._dungeon_run(dungeon=dungeon, team=team, wave_limit=wave_limit,
support_character=support_character) support_character=support_character)
def run(self): def update_double_event_record(self):
self.config.update_battle_pass_quests() """
self.config.update_daily_quests() Pages:
self.check_synthesize() in: Any
self.called_daily_support = False out: page_guide, Survival_Index
self.achieved_daily_quest = False """
self.achieved_weekly_quest = False
self.running_double = False
self.daily_quests = self.config.stored.DailyQuest.load_quests()
self.weekly_quests = self.config.stored.BattlePassWeeklyQuest.load_quests()
# Update double event records # Update double event records
if (self.config.stored.DungeonDouble.is_expired() if (self.config.stored.DungeonDouble.is_expired()
or self.config.stored.DungeonDouble.calyx > 0 or self.config.stored.DungeonDouble.calyx > 0
@ -216,7 +219,8 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
logger.info('Get dungeon double remains') logger.info('Get dungeon double remains')
# UI switches # UI switches
switched = self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) switched = self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
if not switched: if not switched and not self._dungeon_survival_index_top_appear():
logger.info('Reset nav states')
# Nav must at top, reset nav states # Nav must at top, reset nav states
self.ui_goto_main() self.ui_goto_main()
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index) self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
@ -237,6 +241,18 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
self.config.stored.DungeonDouble.relic = relic self.config.stored.DungeonDouble.relic = relic
self.config.stored.DungeonDouble.rogue = rogue self.config.stored.DungeonDouble.rogue = rogue
def run(self):
self.config.update_battle_pass_quests()
self.config.update_daily_quests()
self.check_synthesize()
self.called_daily_support = False
self.achieved_daily_quest = False
self.achieved_weekly_quest = False
self.running_double = False
self.daily_quests = self.config.stored.DailyQuest.load_quests()
self.weekly_quests = self.config.stored.BattlePassWeeklyQuest.load_quests()
self.update_double_event_record()
# Run double events # Run double events
planner = self.planner.get_dungeon(double_calyx=True) planner = self.planner.get_dungeon(double_calyx=True)
# Double calyx # Double calyx
@ -274,14 +290,6 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
final = planner final = planner
self.is_doing_planner = True self.is_doing_planner = True
# Check daily
if self.achieved_daily_quest:
self.config.task_call('DailyQuest')
self.config.task_stop()
if self.achieved_weekly_quest:
self.config.task_call('BattlePass')
self.config.task_stop()
# Use all stamina # Use all stamina
if do_rogue: if do_rogue:
# Use support if prioritize rogue # Use support if prioritize rogue

View File

@ -131,7 +131,7 @@ class DungeonState(UI):
""" """
Delay tasks that use stamina Delay tasks that use stamina
""" """
if dungeon.is_Simulated_Universe: if dungeon.is_Simulated_Universe or dungeon.is_Ornament_Extraction:
limit = 80 limit = 80
elif dungeon.is_Cavern_of_Corrosion: elif dungeon.is_Cavern_of_Corrosion:
limit = 80 limit = 80
@ -182,7 +182,7 @@ class DungeonState(UI):
logger.info(f'Approaching next monday, delay to {next_monday} instead') logger.info(f'Approaching next monday, delay to {next_monday} instead')
future = next_monday future = next_monday
tasks = ['Dungeon', 'Weekly'] tasks = ['Dungeon', 'Weekly', 'Ornament']
with self.config.multi_set(): with self.config.multi_set():
for task in tasks: for task in tasks:
next_run = self.config.cross_get(keys=f'{task}.Scheduler.NextRun', default=DEFAULT_TIME) next_run = self.config.cross_get(keys=f'{task}.Scheduler.NextRun', default=DEFAULT_TIME)

View File

@ -311,6 +311,13 @@ class DungeonUI(DungeonState):
logger.info('Survival index loaded, SURVIVAL_INDEX_OE_LOADED') logger.info('Survival index loaded, SURVIVAL_INDEX_OE_LOADED')
return True return True
def _dungeon_survival_index_top_appear(self):
if self.appear(SURVIVAL_INDEX_SU_LOADED):
return True
if self.appear(SURVIVAL_INDEX_OE_LOADED):
return True
return False
def _dungeon_wait_treasures_lightward_loaded(self, skip_first_screenshot=True): def _dungeon_wait_treasures_lightward_loaded(self, skip_first_screenshot=True):
""" """
Returns: Returns:

View File

@ -5,15 +5,15 @@ from tasks.base.assets.assets_base_page import MAP_EXIT
from tasks.base.assets.assets_base_popup import POPUP_CANCEL from tasks.base.assets.assets_base_popup import POPUP_CANCEL
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
from tasks.combat.assets.assets_combat_support import COMBAT_SUPPORT_LIST from tasks.combat.assets.assets_combat_support import COMBAT_SUPPORT_LIST
from tasks.combat.combat import Combat from tasks.dungeon.dungeon import Dungeon
from tasks.dungeon.event import DungeonEvent from tasks.dungeon.state import DungeonState
from tasks.map.route.loader import RouteLoader from tasks.map.route.loader import RouteLoader
from tasks.map.route.route.daily import OrnamentExtraction__route from tasks.map.route.route.daily import OrnamentExtraction__route
from tasks.ornament.assets.assets_ornament_combat import * from tasks.ornament.assets.assets_ornament_combat import *
from tasks.ornament.assets.assets_ornament_ui import * from tasks.ornament.assets.assets_ornament_ui import *
class OrnamentCombat(DungeonEvent, Combat, RouteLoader): class OrnamentCombat(Dungeon, RouteLoader, DungeonState):
def combat_enter_from_map(self, skip_first_screenshot=True): def combat_enter_from_map(self, skip_first_screenshot=True):
# Don't enter from map, UI too deep inside # Don't enter from map, UI too deep inside
# Enter from survival index instead # Enter from survival index instead
@ -23,23 +23,6 @@ class OrnamentCombat(DungeonEvent, Combat, RouteLoader):
# Different position to OCR # Different position to OCR
return super().get_double_event_remain_at_combat(button) return super().get_double_event_remain_at_combat(button)
def _dungeon_wait_until_dungeon_list_loaded(self, skip_first_screenshot=True):
# Check save file before entering
result = super()._dungeon_wait_until_dungeon_list_loaded(skip_first_screenshot)
if self.image_color_count(
DIVERGENT_UNIVERSE_SAVE_UNAVAILABLE,
color=(195, 89, 79), threshold=221, count=1000,
):
logger.error(
'Divergent Universe save unavailable, '
'please clear Divergent Universe once before running Ornament Extraction'
)
self.config.task_delay(server_update=True)
self.config.task_stop()
return result
def oe_leave(self, skip_first_screenshot=True): def oe_leave(self, skip_first_screenshot=True):
self.interval_clear([COMBAT_PREPARE, MAP_EXIT]) self.interval_clear([COMBAT_PREPARE, MAP_EXIT])
logger.hr('OE leave') logger.hr('OE leave')
@ -121,6 +104,32 @@ class OrnamentCombat(DungeonEvent, Combat, RouteLoader):
self.interval_reset(COMBAT_SUPPORT_LIST) self.interval_reset(COMBAT_SUPPORT_LIST)
continue continue
def combat_get_trailblaze_power(self, expect_reduce=False, skip_first_screenshot=True) -> int:
"""
Args:
expect_reduce: Current value is supposed to be lower than the previous.
skip_first_screenshot:
Returns:
int: Equivalent stamina
Pages:
in: COMBAT_PREPARE or COMBAT_REPEAT
"""
before = self.config.stored.TrailblazePower.value + self.config.stored.Immersifier.value * 40
after = before
for _ in range(3):
self.dungeon_update_stamina()
after = self.config.stored.TrailblazePower.value + self.config.stored.Immersifier.value * 40
if expect_reduce:
if before > after:
break
else:
break
return after
def is_team_prepared(self) -> bool: def is_team_prepared(self) -> bool:
""" """
Pages: Pages:
@ -169,7 +178,11 @@ class OrnamentCombat(DungeonEvent, Combat, RouteLoader):
# End # End
if self.is_in_main(): if self.is_in_main():
logger.info('Combat map entered') logger.info('Combat map entered')
self.device.screenshot_interval_set()
break break
if self.is_combat_executing():
self.device.screenshot_interval_set()
return True
# Relics full # Relics full
# Clicking between COMBAT_PREPARE and COMBAT_TEAM_PREPARE # Clicking between COMBAT_PREPARE and COMBAT_TEAM_PREPARE
if trial > 5: if trial > 5:
@ -187,6 +200,7 @@ class OrnamentCombat(DungeonEvent, Combat, RouteLoader):
if support_set and self.appear(COMBAT_PREPARE, interval=5): if support_set and self.appear(COMBAT_PREPARE, interval=5):
# Long loading after COMBAT_PREPARE # Long loading after COMBAT_PREPARE
self.device.click(COMBAT_PREPARE) self.device.click(COMBAT_PREPARE)
self.device.screenshot_interval_set('combat')
trial += 1 trial += 1
continue continue
if self.handle_popup_confirm(): if self.handle_popup_confirm():

View File

@ -0,0 +1,99 @@
from datetime import timedelta
from module.config.stored.classes import now
from module.config.utils import DEFAULT_TIME
from module.exception import ScriptError
from module.logger import logger
from tasks.dungeon.assets.assets_dungeon_ui_rogue import DIVERGENT_UNIVERSE_SAVE_UNAVAILABLE
from tasks.dungeon.keywords import DungeonList
from tasks.ornament.combat import OrnamentCombat
class Ornament(OrnamentCombat):
# Reuse support, cause exit and re-enter is so time-wasting in Divergent Universe
support_once = False
def _dungeon_wait_until_dungeon_list_loaded(self, skip_first_screenshot=True):
result = super()._dungeon_wait_until_dungeon_list_loaded(skip_first_screenshot)
# Check save file before entering
if self.image_color_count(
DIVERGENT_UNIVERSE_SAVE_UNAVAILABLE,
color=(195, 89, 79), threshold=221, count=1000,
):
logger.error(
'Divergent Universe save unavailable, '
'please clear Divergent Universe once before running Ornament Extraction'
)
self.config.task_delay(server_update=True)
self.config.task_stop()
# Always update double rogue
record = self.config.stored.DungeonDouble.time
if now() - record < timedelta(seconds=5):
# Updated just now
pass
else:
if self.has_double_relic_event():
rogue = self.get_double_rogue_remain()
self.config.stored.DungeonDouble.rogue = rogue
else:
logger.info('No double rogue event')
# Check stamina
logger.info('Check stamina')
stamina = self.combat_get_trailblaze_power()
if stamina < self.combat_wave_cost:
logger.info('Current trailblaze power is not enough for a run')
self.delay_dungeon_task(self.dungeon)
self.config.task_stop()
if not self.config.Ornament_UseStamina and self.config.stored.DungeonDouble.rogue <= 0:
if self.config.stored.Immersifier.value <= 0:
logger.info('Current immersifier is not enough for a run')
self.delay_dungeon_task(self.dungeon)
self.config.task_stop()
return result
def run(self):
self.config.update_battle_pass_quests()
self.config.update_daily_quests()
# self.check_synthesize()
self.called_daily_support = False
self.achieved_daily_quest = False
self.achieved_weekly_quest = False
self.running_double = False
self.daily_quests = self.config.stored.DailyQuest.load_quests()
self.weekly_quests = self.config.stored.BattlePassWeeklyQuest.load_quests()
self.update_double_event_record()
# During double event, do it first
if self.config.stored.DungeonDouble.calyx or self.config.stored.DungeonDouble.relic:
logger.info('During double calyx or relic event, delay Ornament')
future = self.config.cross_get('Dungeon.Scheduler.NextRun', default=DEFAULT_TIME)
future = future + timedelta(minutes=1)
with self.config.multi_set():
self.config.task_delay(target=future)
self.config.task_stop()
# Run
dungeon = DungeonList.find(self.config.Ornament_Dungeon)
self.combat_wave_cost = 40
self.dungeon = dungeon
if self.config.Ornament_UseStamina:
# No limit
self.dungeon_run(dungeon, wave_limit=0)
# Stamina should have exhausted in dungeon_run
raise ScriptError('Ornament finished but stamina was not exhausted')
elif self.config.stored.DungeonDouble.rogue > 0:
# Limited in double events
self.running_double = True
self.dungeon_run(dungeon, wave_limit=self.config.stored.DungeonDouble.rogue)
self.running_double = False
self.config.task_delay(server_update=True)
else:
# Use immersifier only, wave limited in _dungeon_wait_until_dungeon_list_loaded
self.dungeon_run(dungeon, wave_limit=0)
self.config.task_delay(server_update=True)