mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-15 22:19:18 +00:00
commit
ed70251300
BIN
assets/cn/assignment/ui/EVENT_FINISHED.png
Normal file
BIN
assets/cn/assignment/ui/EVENT_FINISHED.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
@ -1,5 +1,3 @@
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
@ -11,7 +9,7 @@ from module.base.decorator import del_cached_property
|
||||
from module.config.config import AzurLaneConfig, TaskEnd
|
||||
from module.config.utils import deep_get, deep_set
|
||||
from module.exception import *
|
||||
from module.logger import logger
|
||||
from module.logger import logger, save_error_log
|
||||
from module.notify import handle_notify
|
||||
|
||||
|
||||
@ -117,6 +115,9 @@ class AzurLaneAutoScript:
|
||||
else:
|
||||
self.checker.wait_until_available()
|
||||
return False
|
||||
except HandledError as e:
|
||||
logger.error(e)
|
||||
return False
|
||||
except ScriptError as e:
|
||||
logger.critical(e)
|
||||
logger.critical('This is likely to be a mistake of developers, but sometimes just random issues')
|
||||
@ -149,33 +150,7 @@ class AzurLaneAutoScript:
|
||||
Save last 60 screenshots in ./log/error/<timestamp>
|
||||
Save logs to ./log/error/<timestamp>/log.txt
|
||||
"""
|
||||
from module.base.utils import save_image
|
||||
from module.handler.sensitive_info import (handle_sensitive_image, handle_sensitive_logs)
|
||||
if self.config.Error_SaveError:
|
||||
folder = f'./log/error/{int(time.time() * 1000)}'
|
||||
logger.warning(f'Saving error: {folder}')
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
for data in self.device.screenshot_deque:
|
||||
image_time = datetime.strftime(data['time'], '%Y-%m-%d_%H-%M-%S-%f')
|
||||
image = handle_sensitive_image(data['image'])
|
||||
save_image(image, f'{folder}/{image_time}.png')
|
||||
if self.device.screenshot_tracking:
|
||||
os.makedirs(f'{folder}/tracking', exist_ok=True)
|
||||
for data in self.device.screenshot_tracking:
|
||||
image_time = datetime.strftime(data['time'], '%Y-%m-%d_%H-%M-%S-%f')
|
||||
with open(f'{folder}/tracking/{image_time}.png', 'wb') as f:
|
||||
f.write(data['image'].getvalue())
|
||||
with open(logger.log_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
start = 0
|
||||
for index, line in enumerate(lines):
|
||||
line = line.strip(' \r\t\n')
|
||||
if re.match('^═{15,}$', line):
|
||||
start = index
|
||||
lines = lines[start - 2:]
|
||||
lines = handle_sensitive_logs(lines)
|
||||
with open(f'{folder}/log.txt', 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
||||
save_error_log(config=self.config, device=self.device)
|
||||
|
||||
def wait_until(self, future):
|
||||
"""
|
||||
|
@ -17,6 +17,12 @@ class GameTooManyClickError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HandledError(Exception):
|
||||
# Error handled before raising
|
||||
# No extra handling required, just retry
|
||||
pass
|
||||
|
||||
|
||||
class EmulatorNotRunningError(Exception):
|
||||
pass
|
||||
|
||||
|
@ -1,3 +1,2 @@
|
||||
from .logger import logger
|
||||
from .logger import set_file_logger, set_func_logger
|
||||
from .logger import WEB_THEME, Highlighter, HTMLConsole
|
||||
from .error import save_error_log
|
||||
from .logger import HTMLConsole, Highlighter, WEB_THEME, logger, set_file_logger, set_func_logger
|
||||
|
44
module/logger/error.py
Normal file
44
module/logger/error.py
Normal file
@ -0,0 +1,44 @@
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from module.logger.logger import logger
|
||||
|
||||
|
||||
def save_error_log(config, device):
|
||||
"""
|
||||
Save last 60 screenshots in ./log/error/<timestamp>
|
||||
Save logs to ./log/error/<timestamp>/log.txt
|
||||
|
||||
Args:
|
||||
config: AzurLaneConfig object
|
||||
device: Device object
|
||||
"""
|
||||
from module.base.utils import save_image
|
||||
from module.handler.sensitive_info import (handle_sensitive_image, handle_sensitive_logs)
|
||||
if config.Error_SaveError:
|
||||
folder = f'./log/error/{int(time.time() * 1000)}'
|
||||
logger.warning(f'Saving error: {folder}')
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
for data in device.screenshot_deque:
|
||||
image_time = datetime.strftime(data['time'], '%Y-%m-%d_%H-%M-%S-%f')
|
||||
image = handle_sensitive_image(data['image'])
|
||||
save_image(image, f'{folder}/{image_time}.png')
|
||||
if device.screenshot_tracking:
|
||||
os.makedirs(f'{folder}/tracking', exist_ok=True)
|
||||
for data in device.screenshot_tracking:
|
||||
image_time = datetime.strftime(data['time'], '%Y-%m-%d_%H-%M-%S-%f')
|
||||
with open(f'{folder}/tracking/{image_time}.png', 'wb') as f:
|
||||
f.write(data['image'].getvalue())
|
||||
with open(logger.log_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
start = 0
|
||||
for index, line in enumerate(lines):
|
||||
line = line.strip(' \r\t\n')
|
||||
if re.match('^═{15,}$', line):
|
||||
start = index
|
||||
lines = lines[start - 2:]
|
||||
lines = handle_sensitive_logs(lines)
|
||||
with open(f'{folder}/log.txt', 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
@ -116,6 +116,17 @@ ENTRY_LOADED = ButtonWrapper(
|
||||
button=(474, 161, 491, 615),
|
||||
),
|
||||
)
|
||||
EVENT_FINISHED = ButtonWrapper(
|
||||
name='EVENT_FINISHED',
|
||||
cn=Button(
|
||||
file='./assets/cn/assignment/ui/EVENT_FINISHED.png',
|
||||
area=(700, 300, 752, 331),
|
||||
search=(680, 280, 772, 351),
|
||||
color=(223, 215, 195),
|
||||
button=(700, 300, 752, 331),
|
||||
),
|
||||
en=None,
|
||||
)
|
||||
EXP_MATERIALS_CREDITS_CHECK = ButtonWrapper(
|
||||
name='EXP_MATERIALS_CREDITS_CHECK',
|
||||
cn=Button(
|
||||
|
@ -35,7 +35,6 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
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)
|
||||
@ -91,12 +90,14 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
logger.info(
|
||||
f'User specified assignments: {", ".join([x.name for x in assignments])}')
|
||||
remain = None
|
||||
insight = False
|
||||
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)
|
||||
self.goto_entry(assignment, insight=insight)
|
||||
insight = True
|
||||
if remain is None:
|
||||
_, remain, _ = self._limit_status
|
||||
status = self._check_assignment_status()
|
||||
@ -106,6 +107,7 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
if status == AssignmentStatus.DISPATCHED:
|
||||
self.dispatched[assignment] = datetime.now() + \
|
||||
self._get_assignment_time()
|
||||
insight = False
|
||||
continue
|
||||
# General assignments must be dispatchable here
|
||||
if remain <= 0:
|
||||
@ -205,8 +207,8 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
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)
|
||||
# Order of entries changes if claimed
|
||||
self.goto_entry(assignment, insight=claimed)
|
||||
status = self._check_assignment_status()
|
||||
if status == AssignmentStatus.LOCKED:
|
||||
continue
|
||||
|
@ -4,6 +4,7 @@ from module.config.stored.classes import now
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import Duration
|
||||
from tasks.assignment.assets.assets_assignment_claim import *
|
||||
from tasks.assignment.assets.assets_assignment_ui import EVENT_FINISHED
|
||||
from tasks.assignment.dispatch import AssignmentDispatch
|
||||
from tasks.assignment.keywords import AssignmentEntry
|
||||
from tasks.base.page import page_assignment
|
||||
@ -79,6 +80,9 @@ class AssignmentClaim(AssignmentDispatch):
|
||||
if self.appear(page_assignment.check_button):
|
||||
logger.info('Assignment report is closed')
|
||||
break
|
||||
if self.appear(EVENT_FINISHED):
|
||||
logger.info('Event finished')
|
||||
return
|
||||
# Close report
|
||||
if self.appear(REPORT, interval=1):
|
||||
self.device.click(click_button)
|
||||
|
@ -105,11 +105,11 @@ class AssignmentDispatch(AssignmentUI):
|
||||
continue
|
||||
# Select
|
||||
if self.interval_is_reached(CHARACTER_1_SELECTED, interval=2):
|
||||
if not self.image_color_count(CHARACTER_1_SELECTED, (240, 240, 240)):
|
||||
if not self.image_color_count(CHARACTER_1_SELECTED, (240, 240, 240), threshold=221, count=160):
|
||||
self.device.click(CHARACTER_1)
|
||||
self.interval_reset(CHARACTER_1_SELECTED, interval=2)
|
||||
if self.interval_is_reached(CHARACTER_2_SELECTED, interval=2):
|
||||
if not self.image_color_count(CHARACTER_2_SELECTED, (240, 240, 240)):
|
||||
if not self.image_color_count(CHARACTER_2_SELECTED, (240, 240, 240), threshold=221, count=160):
|
||||
self.device.click(CHARACTER_2)
|
||||
self.interval_reset(CHARACTER_2_SELECTED, interval=2)
|
||||
|
||||
@ -126,7 +126,8 @@ class AssignmentDispatch(AssignmentUI):
|
||||
# End
|
||||
if self.appear(CONFIRM_ASSIGNMENT):
|
||||
if self.image_color_count(CONFIRM_ASSIGNMENT.button, color=(227, 227, 227), count=1000):
|
||||
logger.info('Characters are all selected (light button)')
|
||||
logger.info(
|
||||
'Characters are all selected (light button)')
|
||||
break
|
||||
if self.appear(CHARACTER_LIST, interval=2):
|
||||
# EMPTY_SLOT appeared above
|
||||
|
@ -40,7 +40,8 @@ class AssignmentOcr(Ocr):
|
||||
(KEYWORDS_ASSIGNMENT_ENTRY.Legend_of_the_Puppet_Master, '^师传说'),
|
||||
(KEYWORDS_ASSIGNMENT_ENTRY.The_Wages_of_Humanity, '[赠]养人类'),
|
||||
(KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Car_Thief, '.*的偷车贼.*'),
|
||||
(KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Synesthesia_Beacon_Function_Iteration, '联觉信标功能[送]代'),
|
||||
(KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Synesthesia_Beacon_Function_Iteration,
|
||||
'联觉信标功能[送]代'),
|
||||
],
|
||||
'en': [
|
||||
# (KEYWORDS_ASSIGNMENT_EVENT_ENTRY.Food_Improvement_Plan.name,
|
||||
@ -156,10 +157,13 @@ class AssignmentUI(UI):
|
||||
self.goto_group(KEYWORDS_ASSIGNMENT_GROUP.Character_Materials)
|
||||
"""
|
||||
if ASSIGNMENT_GROUP_SWITCH.get(self) == group:
|
||||
if not ASSIGNMENT_ENTRY_LIST.cur_buttons:
|
||||
ASSIGNMENT_ENTRY_LIST.load_rows(self)
|
||||
return
|
||||
logger.hr('Assignment group goto', level=3)
|
||||
if ASSIGNMENT_GROUP_SWITCH.set(group, self):
|
||||
self._wait_until_entry_loaded()
|
||||
self._wait_until_correct_entry_loaded(group)
|
||||
|
||||
def goto_entry(self, entry: AssignmentEntry, insight: bool = True):
|
||||
"""
|
||||
@ -213,11 +217,38 @@ class AssignmentUI(UI):
|
||||
if timeout.reached():
|
||||
logger.warning('Wait entry loaded timeout')
|
||||
break
|
||||
if self.appear(EVENT_FINISHED):
|
||||
logger.info('Event finished')
|
||||
break
|
||||
if self.appear(ASSIGNMENT_CHECK) and \
|
||||
self.image_color_count(ENTRY_LOADED, (35, 35, 35), count=800):
|
||||
logger.info('Entry loaded')
|
||||
break
|
||||
|
||||
def _wait_until_correct_entry_loaded(self, group: AssignmentGroup):
|
||||
skip_first_screenshot = True
|
||||
timeout = Timer(3, count=3).start()
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if timeout.reached():
|
||||
logger.warning('Wait correct entry loaded timeout')
|
||||
break
|
||||
if self.appear(EVENT_FINISHED):
|
||||
logger.info('Event finished')
|
||||
break
|
||||
|
||||
ASSIGNMENT_ENTRY_LIST.load_rows(self)
|
||||
if all(
|
||||
x.matched_keyword.group == group
|
||||
for x in ASSIGNMENT_ENTRY_LIST.cur_buttons
|
||||
):
|
||||
logger.info('Correct entry loaded')
|
||||
break
|
||||
|
||||
@property
|
||||
def _limit_status(self) -> tuple[int, int, int]:
|
||||
self.device.screenshot()
|
||||
@ -279,7 +310,7 @@ class AssignmentUI(UI):
|
||||
"""
|
||||
Iterate entries from top to bottom
|
||||
"""
|
||||
ASSIGNMENT_ENTRY_LIST.load_rows(main=self)
|
||||
# load_rows is done in goto_group already
|
||||
# Freeze ocr results here
|
||||
yield from [
|
||||
button.matched_keyword
|
||||
|
@ -11,6 +11,8 @@ DICT_SORTED_RANGES = {
|
||||
Welt,
|
||||
Aventurine,
|
||||
FuXuan,
|
||||
# Slow bullet
|
||||
Robin,
|
||||
# Longer precast
|
||||
BlackSwan,
|
||||
],
|
||||
|
@ -86,6 +86,8 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
|
||||
support_set = False
|
||||
else:
|
||||
support_set = True
|
||||
# Reset combat_wave_cost, so handle_combat_interact() won't activate before handle_combat_prepare()
|
||||
self.combat_wave_cost = 10
|
||||
logger.info([support_character, support_set])
|
||||
trial = 0
|
||||
while 1:
|
||||
|
@ -135,6 +135,7 @@ class MapControl(Combat, AimDetectorMixin):
|
||||
logger.info(f'Goto {waypoint}')
|
||||
self.screenshot_tracking_add()
|
||||
self.waypoint = waypoint
|
||||
waypoint.unexpected_confirm.reset()
|
||||
self.device.stuck_record_clear()
|
||||
self.device.click_record_clear()
|
||||
|
||||
@ -176,6 +177,12 @@ class MapControl(Combat, AimDetectorMixin):
|
||||
if waypoint.early_stop:
|
||||
return result
|
||||
if self.walk_additional():
|
||||
# Clearing items may trigger additional popups
|
||||
if attacked_item.started() and attacked_item.reached():
|
||||
logger.info('Walk result add: item')
|
||||
result.append('item')
|
||||
if 'item' in waypoint.expected_end and waypoint.early_stop:
|
||||
return result
|
||||
attacked_enemy.clear()
|
||||
attacked_item.clear()
|
||||
continue
|
||||
@ -227,7 +234,7 @@ class MapControl(Combat, AimDetectorMixin):
|
||||
if attacked_item.started() and attacked_item.reached():
|
||||
logger.info('Walk result add: item')
|
||||
result.append('item')
|
||||
if waypoint.early_stop:
|
||||
if 'item' in waypoint.expected_end and waypoint.early_stop:
|
||||
return result
|
||||
if waypoint.interact_radius > 0:
|
||||
if diff < waypoint.interact_radius:
|
||||
|
@ -164,6 +164,11 @@ class RogueEntry(RouteBase, RogueRewardHandler, RoguePathHandler, DungeonUI):
|
||||
if interval.reached() and self.is_page_rogue_main():
|
||||
self.device.click(THEME_SWITCH)
|
||||
interval.reset()
|
||||
# Weekly refresh popup
|
||||
if self.appear_then_click(REWARD_CLOSE, interval=2):
|
||||
continue
|
||||
if self.handle_reward():
|
||||
continue
|
||||
|
||||
def _rogue_world_set(self, world: int | DungeonList, skip_first_screenshot=True):
|
||||
"""
|
||||
|
@ -20,6 +20,10 @@ class RouteBase(RouteBase_, RogueExit, RogueEvent, RogueReward):
|
||||
enroute_add_item = True
|
||||
|
||||
def combat_expected_end(self):
|
||||
# Curio effect, that drops curio after combat
|
||||
if self.handle_blessing_popup():
|
||||
return False
|
||||
# Blessings after combat
|
||||
if self.is_page_choose_blessing():
|
||||
logger.info('Combat ended at is_page_choose_blessing()')
|
||||
return True
|
||||
|
@ -4,7 +4,10 @@ import numpy as np
|
||||
|
||||
from module.base.decorator import cached_property
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.exception import GameStuckError, HandledError
|
||||
from module.logger import logger, save_error_log
|
||||
from tasks.base.assets.assets_base_main_page import ROGUE_LEAVE_FOR_NOW
|
||||
from tasks.base.assets.assets_base_page import MAP_EXIT
|
||||
from tasks.character.switch import CharacterSwitch
|
||||
from tasks.map.keywords import MapPlane
|
||||
from tasks.map.keywords.plane import (
|
||||
@ -17,6 +20,8 @@ from tasks.map.keywords.plane import (
|
||||
from tasks.map.minimap.minimap import Minimap
|
||||
from tasks.map.resource.resource import SPECIAL_PLANES
|
||||
from tasks.map.route.loader import RouteLoader as RouteLoader_
|
||||
from tasks.rogue.assets.assets_rogue_ui import BLESSING_CONFIRM
|
||||
from tasks.rogue.assets.assets_rogue_weekly import ROGUE_REPORT
|
||||
from tasks.rogue.blessing.ui import RogueUI
|
||||
from tasks.rogue.route.base import RouteBase
|
||||
from tasks.rogue.route.model import RogueRouteListModel, RogueRouteModel
|
||||
@ -75,7 +80,7 @@ class MinimapWrapper:
|
||||
return self.all_minimap[route.plane_floor]
|
||||
|
||||
|
||||
class RouteLoader(RogueUI, MinimapWrapper, RouteLoader_, CharacterSwitch):
|
||||
class RouteLoader(RouteBase, MinimapWrapper, RouteLoader_, CharacterSwitch):
|
||||
def position_find_known(self, image, force_return=False) -> Optional[RogueRouteModel]:
|
||||
"""
|
||||
Try to find from known route spawn point
|
||||
@ -271,6 +276,43 @@ class RouteLoader(RogueUI, MinimapWrapper, RouteLoader_, CharacterSwitch):
|
||||
if route is not None:
|
||||
return route
|
||||
|
||||
def rogue_leave(self, skip_first_screenshot=True):
|
||||
logger.hr('Rogue leave', level=1)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# End
|
||||
if self.is_page_rogue_main():
|
||||
logger.info('Rogue left')
|
||||
break
|
||||
|
||||
# Re-enter
|
||||
if self.handle_combat_interact():
|
||||
continue
|
||||
# From ui_leave_special
|
||||
if self.is_in_map_exit(interval=2):
|
||||
self.device.click(MAP_EXIT)
|
||||
continue
|
||||
if self.handle_popup_confirm():
|
||||
continue
|
||||
if self.appear_then_click(ROGUE_LEAVE_FOR_NOW, interval=2):
|
||||
continue
|
||||
# Blessing
|
||||
if self.handle_blessing():
|
||||
continue
|
||||
# _domain_exit_wait_next()
|
||||
if self.match_template_color(ROGUE_REPORT, interval=2):
|
||||
logger.info(f'{ROGUE_REPORT} -> {BLESSING_CONFIRM}')
|
||||
self.device.click(BLESSING_CONFIRM)
|
||||
continue
|
||||
if self.handle_reward():
|
||||
continue
|
||||
if self.handle_get_character():
|
||||
continue
|
||||
|
||||
def route_run(self, route=None):
|
||||
"""
|
||||
Run a rogue domain
|
||||
@ -286,7 +328,15 @@ class RouteLoader(RogueUI, MinimapWrapper, RouteLoader_, CharacterSwitch):
|
||||
# To have a newer image, since previous loadings took some time
|
||||
route = self.position_find(skip_first_screenshot=False)
|
||||
self.screenshot_tracking_add()
|
||||
super().route_run(route)
|
||||
|
||||
try:
|
||||
super().route_run(route)
|
||||
return True
|
||||
except GameStuckError as e:
|
||||
logger.error(e)
|
||||
save_error_log(config=self.config, device=self.device)
|
||||
self.rogue_leave()
|
||||
raise HandledError('Rogue run failed')
|
||||
|
||||
def rogue_run(self, skip_first_screenshot=True):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user