Merge branch 'master' into dev

This commit is contained in:
LmeSzinc 2024-05-21 00:46:11 +08:00
commit 408971892c
16 changed files with 189 additions and 46 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -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):
"""

View File

@ -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

View File

@ -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
View 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)

View File

@ -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(

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -11,6 +11,8 @@ DICT_SORTED_RANGES = {
Welt,
Aventurine,
FuXuan,
# Slow bullet
Robin,
# Longer precast
BlackSwan,
],

View File

@ -91,6 +91,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:

View File

@ -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:

View File

@ -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):
"""

View File

@ -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

View File

@ -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):
"""