From 0fd15a26c40a310083e549b0113495626d9466c2 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 14 Apr 2023 18:29:19 +0800 Subject: [PATCH 01/16] Fix: The task waited is not the first task (fixed #2489) --- alas.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/alas.py b/alas.py index 126b5532a..229cbfc02 100644 --- a/alas.py +++ b/alas.py @@ -21,6 +21,11 @@ class AzurLaneAutoScript: def __init__(self, config_name='alas'): logger.hr('Start', level=0) self.config_name = config_name + # Skip first restart + self.is_first_task = True + # Failure count of tasks + # Key: str, task name, value: int, failure count + self.failure_record = {} @cached_property def config(self): @@ -427,6 +432,7 @@ class AzurLaneAutoScript: if task.next_run > datetime.now(): logger.info(f'Wait until {task.next_run} for task `{task.command}`') + self.is_first_task = False method = self.config.Optimization_WhenTaskQueueEmpty if method == 'close_game': logger.info('Close game during wait') @@ -434,7 +440,7 @@ class AzurLaneAutoScript: release_resources() self.device.release_during_wait() if not self.wait_until(task.next_run): - del self.__dict__['config'] + del_cached_property(self, 'config') continue self.run('start') elif method == 'goto_main': @@ -443,21 +449,21 @@ class AzurLaneAutoScript: release_resources() self.device.release_during_wait() if not self.wait_until(task.next_run): - del self.__dict__['config'] + del_cached_property(self, 'config') continue elif method == 'stay_there': logger.info('Stay there during wait') release_resources() self.device.release_during_wait() if not self.wait_until(task.next_run): - del self.__dict__['config'] + del_cached_property(self, 'config') continue else: logger.warning(f'Invalid Optimization_WhenTaskQueueEmpty: {method}, fallback to stay_there') release_resources() self.device.release_during_wait() if not self.wait_until(task.next_run): - del self.__dict__['config'] + del_cached_property(self, 'config') continue break @@ -467,8 +473,6 @@ class AzurLaneAutoScript: def loop(self): logger.set_file_logger(self.config_name) logger.info(f'Start scheduler loop: {self.config_name}') - is_first = True - failure_record = {} while 1: # Check update event from GUI @@ -492,10 +496,10 @@ class AzurLaneAutoScript: # Init device and change server _ = self.device # Skip first restart - if is_first and task == 'Restart': + if self.is_first_task and task == 'Restart': logger.info('Skip task `Restart` at scheduler start') self.config.task_delay(server_update=True) - del self.__dict__['config'] + del_cached_property(self, 'config') continue # Run @@ -505,12 +509,12 @@ class AzurLaneAutoScript: logger.hr(task, level=0) success = self.run(inflection.underscore(task)) logger.info(f'Scheduler: End task `{task}`') - is_first = False + self.is_first_task = False # Check failures - failed = deep_get(failure_record, keys=task, default=0) + failed = deep_get(self.failure_record, keys=task, default=0) failed = 0 if success else failed + 1 - deep_set(failure_record, keys=task, value=failed) + deep_set(self.failure_record, keys=task, value=failed) if failed >= 3: logger.critical(f"Task `{task}` failed 3 or more times.") logger.critical("Possible reason #1: You haven't used it correctly. " @@ -526,11 +530,11 @@ class AzurLaneAutoScript: exit(1) if success: - del self.__dict__['config'] + del_cached_property(self, 'config') continue elif self.config.Error_HandleError: # self.config.task_delay(success=False) - del self.__dict__['config'] + del_cached_property(self, 'config') self.checker.check_now() continue else: From 7bb5c15788a2a523cdd129f42b0a98f5666e739f Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 14 Apr 2023 18:30:46 +0800 Subject: [PATCH 02/16] Fix: Recheck AP boxes when having OCR error --- module/os_handler/action_point.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/module/os_handler/action_point.py b/module/os_handler/action_point.py index aa3e9e047..dcc2cbddd 100644 --- a/module/os_handler/action_point.py +++ b/module/os_handler/action_point.py @@ -160,12 +160,19 @@ class ActionPointHandler(UI, MapEventHandler): # Having too many current AP, probably an OCR error if self._action_point_current > 600: continue + + oil, boxes = self._action_point_box[0], self._action_point_box[1:] # Having boxes - if sum(self._action_point_box[1:]) > 0: - break + if sum(boxes) > 0: + if oil > 100: + break + else: + # [11, 0, 1, 0] + continue # Or having oil # Might be 0 or 1 when page is not fully loaded - if self._action_point_box[0] > 100: + # [1, 0, 0, 0] + if oil > 100: break @staticmethod From dcd542659fb44af751cdda54a837ad07162e74e8 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 14 Apr 2023 19:07:47 +0800 Subject: [PATCH 03/16] Upd: [CN] TEMPLATE_ENCIRCLING_GRAF_SPEE (fixed #2492) --- .../TEMPLATE_ENCIRCLING_GRAF_SPEE.png | Bin 37170 -> 12001 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/cn/war_archives/TEMPLATE_ENCIRCLING_GRAF_SPEE.png b/assets/cn/war_archives/TEMPLATE_ENCIRCLING_GRAF_SPEE.png index cf1bd8acb998fcc141295cdfd64dbf5de57a5cea..5c04432ee64e795026a4bfd2b52347f66e11f681 100644 GIT binary patch delta 9368 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 UH7}6N00000NkvXX1g=70g10p)m;e9( From d86e8907a2efa91de8397dfadf7cbbc07e802b6e Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Fri, 14 Apr 2023 23:14:44 +0800 Subject: [PATCH 04/16] Add: Handle LOGIN_ANNOUNCE in ui_additional (#2494) --- module/ui/ui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module/ui/ui.py b/module/ui/ui.py index 2a722fcce..4c2f65728 100644 --- a/module/ui/ui.py +++ b/module/ui/ui.py @@ -558,6 +558,8 @@ class UI(InfoHandler): # Login if self.appear_then_click(LOGIN_CHECK, offset=(30, 30), interval=3): return True + if self.appear_then_click(LOGIN_ANNOUNCE, offset=(30, 30), interval=3): + return True # Mistaken click if self.appear(EXERCISE_PREPARATION, interval=3): From d1b1b0ac7d4d20961edf85bf6f02307cf69e0fd8 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 15 Apr 2023 22:16:40 +0800 Subject: [PATCH 05/16] Opt: No map clear percentage waiting in SOS maps --- campaign/campaign_sos/campaign_10_5.py | 6 +++--- campaign/campaign_sos/campaign_3_5.py | 6 +++--- campaign/campaign_sos/campaign_4_5.py | 6 +++--- campaign/campaign_sos/campaign_5_5.py | 6 +++--- campaign/campaign_sos/campaign_6_5.py | 6 +++--- campaign/campaign_sos/campaign_7_5.py | 6 +++--- campaign/campaign_sos/campaign_8_5.py | 6 +++--- campaign/campaign_sos/campaign_9_5.py | 6 +++--- campaign/campaign_sos/campaign_base.py | 4 ++++ module/config/config_manual.py | 3 ++- module/map/map_operation.py | 3 +++ 11 files changed, 33 insertions(+), 25 deletions(-) diff --git a/campaign/campaign_sos/campaign_10_5.py b/campaign/campaign_sos/campaign_10_5.py index fff50a3bb..9795fc11a 100644 --- a/campaign/campaign_sos/campaign_10_5.py +++ b/campaign/campaign_sos/campaign_10_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'I6' @@ -42,7 +42,7 @@ A6, B6, C6, D6, E6, F6, G6, H6, I6, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -80,4 +80,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_6(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_3_5.py b/campaign/campaign_sos/campaign_3_5.py index ec22e32ee..9c9d9c1c0 100644 --- a/campaign/campaign_sos/campaign_3_5.py +++ b/campaign/campaign_sos/campaign_3_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'H5' @@ -36,7 +36,7 @@ A5, B5, C5, D5, E5, F5, G5, H5, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -81,4 +81,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_3(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_4_5.py b/campaign/campaign_sos/campaign_4_5.py index 9b58d4dcd..1fe698e7e 100644 --- a/campaign/campaign_sos/campaign_4_5.py +++ b/campaign/campaign_sos/campaign_4_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'H7' @@ -43,7 +43,7 @@ A7, B7, C7, D7, E7, F7, G7, H7, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -91,4 +91,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_4(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_5_5.py b/campaign/campaign_sos/campaign_5_5.py index 9aa77bcca..4f74b686c 100644 --- a/campaign/campaign_sos/campaign_5_5.py +++ b/campaign/campaign_sos/campaign_5_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'H6' @@ -40,7 +40,7 @@ A6, B6, C6, D6, E6, F6, G6, H6, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -85,4 +85,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_4(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_6_5.py b/campaign/campaign_sos/campaign_6_5.py index 7a927d0e5..e1ef6eb04 100644 --- a/campaign/campaign_sos/campaign_6_5.py +++ b/campaign/campaign_sos/campaign_6_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'H6' @@ -41,7 +41,7 @@ A6, B6, C6, D6, E6, F6, G6, H6, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -73,4 +73,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_5(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_7_5.py b/campaign/campaign_sos/campaign_7_5.py index 983457329..b4984f089 100644 --- a/campaign/campaign_sos/campaign_7_5.py +++ b/campaign/campaign_sos/campaign_7_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'H6' @@ -41,7 +41,7 @@ A6, B6, C6, D6, E6, F6, G6, H6, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -76,4 +76,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_5(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_8_5.py b/campaign/campaign_sos/campaign_8_5.py index 4cbdfb8eb..300b24f03 100644 --- a/campaign/campaign_sos/campaign_8_5.py +++ b/campaign/campaign_sos/campaign_8_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'H7' @@ -43,7 +43,7 @@ A7, B7, C7, D7, E7, F7, G7, H7, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -87,4 +87,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_4(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_9_5.py b/campaign/campaign_sos/campaign_9_5.py index e5c7e7fb4..d0dd0e6f1 100644 --- a/campaign/campaign_sos/campaign_9_5.py +++ b/campaign/campaign_sos/campaign_9_5.py @@ -2,7 +2,7 @@ from module.logger import logger from module.map.map_base import CampaignMap from module.map.map_grids import RoadGrids, SelectedGrids -from .campaign_base import CampaignBase +from .campaign_base import CampaignBase, ConfigBase MAP = CampaignMap('SOS') MAP.shape = 'I6' @@ -41,7 +41,7 @@ A6, B6, C6, D6, E6, F6, G6, H6, I6, \ = MAP.flatten() -class Config: +class Config(ConfigBase): # ===== Start of generated config ===== MAP_HAS_MAP_STORY = False MAP_HAS_FLEET_STEP = False @@ -72,4 +72,4 @@ class Campaign(CampaignBase): return self.battle_default() def battle_5(self): - self.fleet_boss.clear_boss() + return self.fleet_boss.clear_boss() diff --git a/campaign/campaign_sos/campaign_base.py b/campaign/campaign_sos/campaign_base.py index 9d2d9122a..3e9e66a4b 100644 --- a/campaign/campaign_sos/campaign_base.py +++ b/campaign/campaign_sos/campaign_base.py @@ -5,6 +5,10 @@ from module.exception import CampaignNameError from module.template.assets import TEMPLATE_STAGE_SOS +class ConfigBase: + MAP_HAS_CLEAR_PERCENTAGE = False + + class CampaignBase(CampaignBase_): ENEMY_FILTER = '1T > 1L > 1E > 1M > 2T > 2L > 2E > 2M > 3T > 3L > 3E > 3M' diff --git a/module/config/config_manual.py b/module/config/config_manual.py index fa9c9aaa7..9cdb8d004 100644 --- a/module/config/config_manual.py +++ b/module/config/config_manual.py @@ -107,6 +107,7 @@ class ManualConfig: """ module.map.fleet """ + MAP_HAS_CLEAR_PERCENTAGE = True MAP_HAS_AMBUSH = True MAP_HAS_FLEET_STEP = False MAP_HAS_MOVABLE_ENEMY = False @@ -339,7 +340,7 @@ class ManualConfig: module.shop """ # For dev purpose, auto extract new item templates - SHOP_EXTRACT_TEMPLATE = False + SHOP_EXTRACT_TEMPLATE = True """ module.war_archives diff --git a/module/map/map_operation.py b/module/map/map_operation.py index dc5305ffb..1f1fa28df 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -251,6 +251,9 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand self.map_clear_percentage_prev = -1 self.map_clear_percentage_timer.reset() return False + if not self.config.MAP_HAS_CLEAR_PERCENTAGE: + logger.attr('MAP_HAS_CLEAR_PERCENTAGE', self.config.MAP_HAS_CLEAR_PERCENTAGE) + return True percent = self.get_map_clear_percentage() logger.attr('Map_clear_percentage', percent) From 456f8ea275c16e7eadd62bcded0a2ca856d8aa29 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 15 Apr 2023 22:19:31 +0800 Subject: [PATCH 06/16] Fix: Allow negative values in location2node() (#2446) --- module/base/utils.py | 131 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 8 deletions(-) diff --git a/module/base/utils.py b/module/base/utils.py index dd7d9af05..cf7fadd0c 100644 --- a/module/base/utils.py +++ b/module/base/utils.py @@ -1,7 +1,11 @@ +import re + import cv2 import numpy as np from PIL import Image +REGEX_NODE = re.compile(r'(-?[A-Za-z]+)(-?\d+)') + def random_normal_distribution_int(a, b, n=3): """Generate a normal distribution int within the interval. Use the average value of several random numbers to @@ -175,6 +179,7 @@ def ensure_int(*args): Returns: list: """ + def to_int(item): try: return int(item) @@ -327,7 +332,7 @@ def area_cross_area(area1, area2, threshold=5): xa1, ya1, xa2, ya2 = area1 xb1, yb1, xb2, yb2 = area2 return abs(xb2 + xb1 - xa2 - xa1) <= xa2 - xa1 + xb2 - xb1 + threshold * 2 \ - and abs(yb2 + yb1 - ya2 - ya1) <= ya2 - ya1 + yb2 - yb1 + threshold * 2 + and abs(yb2 + yb1 - ya2 - ya1) <= ya2 - ya1 + yb2 - yb1 + threshold * 2 def float2str(n, decimal=3): @@ -355,26 +360,136 @@ def point2str(x, y, length=4): return '(%s, %s)' % (str(int(x)).rjust(length), str(int(y)).rjust(length)) -def node2location(node): +def col2name(col): """ + Convert a zero indexed column cell reference to a string. + Args: - node(str): Example: 'E3' + col: The cell column. Int. Returns: - tuple: Example: (6, 4) + Column style string. + + Examples: + 0 -> A, 3 -> D, 35 -> AJ, -1 -> -A """ - return ord(node[0]) % 32 - 1, int(node[1:]) - 1 + + col_neg = col < 0 + if col_neg: + col_num = -col + else: + col_num = col + 1 # Change to 1-index. + col_str = '' + + while col_num: + # Set remainder from 1 .. 26 + remainder = col_num % 26 + + if remainder == 0: + remainder = 26 + + # Convert the remainder to a character. + col_letter = chr(remainder + 64) + + # Accumulate the column letters, right to left. + col_str = col_letter + col_str + + # Get the next order of magnitude. + col_num = int((col_num - 1) / 26) + + if col_neg: + return '-' + col_str + else: + return col_str + + +def name2col(col_str): + """ + Convert a cell reference in A1 notation to a zero indexed row and column. + + Args: + col_str: A1 style string. + + Returns: + row, col: Zero indexed cell row and column indices. + """ + # Convert base26 column string to number. + expn = 0 + col = 0 + col_neg = col_str.startswith('-') + col_str = col_str.strip('-').upper() + + for char in reversed(col_str): + col += (ord(char) - 64) * (26 ** expn) + expn += 1 + + if col_neg: + return -col + else: + return col - 1 # Convert 1-index to zero-index + + +def node2location(node): + """ + See location2node() + + Args: + node (str): Example: 'E3' + + Returns: + tuple[int]: Example: (4, 2) + """ + res = REGEX_NODE.search(node) + if res: + x, y = res.group(1), res.group(2) + y = int(y) + if y > 0: + y -= 1 + return name2col(x), y + else: + # Whatever + return ord(node[0]) % 32 - 1, int(node[1:]) - 1 def location2node(location): """ + Convert location tuple to an Excel-like cell. + Accept negative values also. + + -2 -1 0 1 2 3 + -2 -B-2 -A-2 A-2 B-2 C-2 D-2 + -1 -B-1 -A-1 A-1 B-1 C-1 D-1 + 0 -B1 -A1 A1 B1 C1 D1 + 1 -B2 -A2 A2 B2 C2 D2 + 2 -B3 -A3 A3 B3 C3 D3 + 3 -B4 -A4 A4 B4 C4 D4 + + # To generate the table above + index = range(-2, 4) + row = ' ' + ' '.join([str(i).rjust(4) for i in index]) + print(row) + for y in index: + row = str(y).rjust(2) + ' ' + ' '.join([location2node((x, y)).rjust(4) for x in index]) + print(row) + + def check(node): + return point2str(*node2location(location2node(node)), length=2) + row = ' ' + ' '.join([str(i).rjust(8) for i in index]) + print(row) + for y in index: + row = str(y).rjust(2) + ' ' + ' '.join([check((x, y)).rjust(4) for x in index]) + print(row) + Args: - location(tuple): Example: (6, 4) + location (tuple[int]): Returns: - str: Example: 'E3' + str: """ - return chr(location[0] + 64 + 1) + str(location[1] + 1) + x, y = location + if y >= 0: + y += 1 + return col2name(x) + str(y) def load_image(file, area=None): From 69b01a871c832b9962f72b5b15bedeb59bc7ea75 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 15 Apr 2023 22:20:57 +0800 Subject: [PATCH 07/16] Dev: CodeGenerator for nested expressions --- module/config/code_generator.py | 97 ++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/module/config/code_generator.py b/module/config/code_generator.py index 0c1ab84c3..b2db858b2 100644 --- a/module/config/code_generator.py +++ b/module/config/code_generator.py @@ -2,7 +2,7 @@ import typing as t class TabWrapper: - def __init__(self, generator, prefix='', suffix=''): + def __init__(self, generator, prefix='', suffix='', newline=True): """ Args: generator (CodeGenerator): @@ -10,10 +10,13 @@ class TabWrapper: self.generator = generator self.prefix = prefix self.suffix = suffix + self.newline = newline + + self.nested = False def __enter__(self): - if self.prefix: - self.generator.add(self.prefix) + if not self.nested and self.prefix: + self.generator.add(self.prefix, newline=self.newline) self.generator.tab_count += 1 return self @@ -22,6 +25,13 @@ class TabWrapper: if self.suffix: self.generator.add(self.suffix) + def __repr__(self): + return self.prefix + + def set_nested(self, suffix=''): + self.nested = True + self.suffix += suffix + class CodeGenerator: def __init__(self): @@ -52,25 +62,29 @@ class CodeGenerator: return out def _repr(self, obj): - if isinstance(obj, str) and '\n' in obj: - out = '"""\n' - with self.tab(): - for line in obj.strip().split('\n'): - line = line.strip() - out += self._line_with_tabs(line) - out += self._line_with_tabs('"""', newline=False) - return out + if isinstance(obj, str): + if '\n' in obj: + out = '"""\n' + with self.tab(): + for line in obj.strip().split('\n'): + line = line.strip() + out += self._line_with_tabs(line) + out += self._line_with_tabs('"""', newline=False) + return out return repr(obj) def tab(self): return TabWrapper(self) - def Import(self, text): + def Empty(self): + self.add('') + + def Import(self, text, empty=2): for line in text.strip().split('\n'): line = line.strip() self.add(line) - self.add('') - self.add('') + for _ in range(empty): + self.Empty() def Value(self, key=None, value=None, **kwargs): if key is not None: @@ -83,14 +97,55 @@ class CodeGenerator: line = line.strip() self.add(line, comment=True) - def Dict(self, key): - return TabWrapper(self, prefix=str(key) + ' = {', suffix='}') - - def DictItem(self, key=None, value=None, **kwargs): + def List(self, key=None): if key is not None: - self.add(f'{self._repr(key)}: {self._repr(value)},') - for key, value in kwargs.items(): - self.DictItem(key, value) + return TabWrapper(self, prefix=str(key) + ' = [', suffix=']') + else: + return TabWrapper(self, prefix='[', suffix=']', newline=False) + + def ListItem(self, value): + if isinstance(value, TabWrapper): + value.set_nested(suffix=',') + self.add(f'{self._repr(value)}') + return value + else: + self.add(f'{self._repr(value)},') + + def Dict(self, key=None): + if key is not None: + return TabWrapper(self, prefix=str(key) + ' = {', suffix='}') + else: + return TabWrapper(self, prefix='{', suffix='}', newline=False) + + def DictItem(self, key=None, value=None): + if isinstance(value, TabWrapper): + value.set_nested(suffix=',') + if key is not None: + self.add(f'{self._repr(key)}: {self._repr(value)}') + return value + else: + if key is not None: + self.add(f'{self._repr(key)}: {self._repr(value)},') + + def Object(self, object_class, key=None): + if key is not None: + return TabWrapper(self, prefix=f'{key} = {object_class}(', suffix=')') + else: + return TabWrapper(self, prefix='(', suffix=')', newline=False) + + def ObjectAttr(self, key=None, value=None): + if isinstance(value, TabWrapper): + value.set_nested(suffix=',') + if key is None: + self.add(f'{self._repr(value)}') + else: + self.add(f'{key}={self._repr(value)}') + return value + else: + if key is None: + self.add(f'{self._repr(value)},') + else: + self.add(f'{key}={self._repr(value)},') generator = CodeGenerator() From 590435f42cd579f85e3c1f74d310996b89584597 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 15 Apr 2023 22:28:08 +0800 Subject: [PATCH 08/16] Fix: Handle OCR error in campaign_ensure_chapter() due to slow animation --- module/campaign/campaign_ui.py | 39 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/module/campaign/campaign_ui.py b/module/campaign/campaign_ui.py index 13f30f24d..95dcb1199 100644 --- a/module/campaign/campaign_ui.py +++ b/module/campaign/campaign_ui.py @@ -1,3 +1,4 @@ +from module.base.timer import Timer from module.campaign.assets import * from module.campaign.campaign_event import CampaignEvent from module.campaign.campaign_ocr import CampaignOcr @@ -17,17 +18,45 @@ MODE_SWITCH_2.add_status('ex', SWITCH_2_EX) class CampaignUI(CampaignEvent, CampaignOcr): ENTRANCE = Button(area=(), color=(), button=(), name='default_button') - def campaign_ensure_chapter(self, index): + def campaign_ensure_chapter(self, index, skip_first_screenshot=True): """ Args: index (int, str): Chapter. Such as 7, 'd', 'sp'. + skip_first_screenshot: """ index = self._campaign_get_chapter_index(index) - # A tricky way to use ui_ensure_index. - self.ui_ensure_index(index, letter=self.get_chapter_index, - prev_button=CHAPTER_PREV, next_button=CHAPTER_NEXT, - fast=True, skip_first_screenshot=True) + # A copy of use ui_ensure_index. + logger.hr("UI ensure index") + retry = Timer(1, count=2) + error_confirm = Timer(0.2, count=0) + while 1: + if skip_first_screenshot: + skip_first_screenshot = False + else: + self.device.screenshot() + + current = self.get_chapter_index(self.device.image) + + logger.attr("Index", current) + logger.info([index, current, index - current]) + diff = index - current + if diff == 0: + break + + # 14-4 may be OCR as 4-1 due to slow animation, confirm if it is 4-1 + if index >= 11 and index % 10 == current: + error_confirm.start() + if not error_confirm.reached(): + continue + else: + error_confirm.reset() + + # Switch + if retry.reached(): + button = CHAPTER_NEXT if diff > 0 else CHAPTER_PREV + self.device.multi_click(button, n=abs(diff), interval=(0.2, 0.3)) + retry.reset() def campaign_ensure_mode(self, mode='normal'): """ From 64db1406a03bdd71b8e9a05f0e85e563ed0e8334 Mon Sep 17 00:00:00 2001 From: guoh064 <50830808+guoh064@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:48:20 +0800 Subject: [PATCH 09/16] Fix: Handle 2 Infobars covering action_point ocr (#2503) --- module/os_handler/action_point.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/module/os_handler/action_point.py b/module/os_handler/action_point.py index dcc2cbddd..689372de9 100644 --- a/module/os_handler/action_point.py +++ b/module/os_handler/action_point.py @@ -150,6 +150,9 @@ class ActionPointHandler(UI, MapEventHandler): skip_first_screenshot = False else: self.device.screenshot() + + if self.info_bar_count() >= 2: + continue if timeout.reached(): logger.warning('Get action points timeout') From cb2b39854648cd6db269cbd6bad9e1e018154b21 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:54:38 +0800 Subject: [PATCH 10/16] Fix: Reset timeout timer in action_point_safe_get() --- module/os_handler/action_point.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/module/os_handler/action_point.py b/module/os_handler/action_point.py index 689372de9..87d0e9c21 100644 --- a/module/os_handler/action_point.py +++ b/module/os_handler/action_point.py @@ -150,14 +150,15 @@ class ActionPointHandler(UI, MapEventHandler): skip_first_screenshot = False else: self.device.screenshot() - - if self.info_bar_count() >= 2: - continue if timeout.reached(): logger.warning('Get action points timeout') break + if self.info_bar_count() >= 2: + timeout.reset() + continue + self.action_point_update() # Having too many current AP, probably an OCR error From 343520cce7782e3788a1dc562cc321e42a45f293 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 1 Apr 2023 02:32:13 +0800 Subject: [PATCH 11/16] Dep: Add uiautomator2cache --- deploy/AidLux/0.92/requirements.txt | 3 ++- deploy/AidLux/requirements_generator.py | 4 +++- deploy/docker/requirements.txt | 3 ++- deploy/docker/requirements_generator.py | 10 ++++------ deploy/logger.py | 7 +++++-- requirements-in.txt | 3 ++- requirements.txt | 1 + 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/deploy/AidLux/0.92/requirements.txt b/deploy/AidLux/0.92/requirements.txt index 4faa60718..99cc87806 100644 --- a/deploy/AidLux/0.92/requirements.txt +++ b/deploy/AidLux/0.92/requirements.txt @@ -26,4 +26,5 @@ pyzmq==22.3.0 onepush pydantic psutil==5.9.3 -av==10.0.0 \ No newline at end of file +av==10.0.0 +uiautomator2cache==0.3.0 \ No newline at end of file diff --git a/deploy/AidLux/requirements_generator.py b/deploy/AidLux/requirements_generator.py index 15d5effbe..14ed0f642 100644 --- a/deploy/AidLux/requirements_generator.py +++ b/deploy/AidLux/requirements_generator.py @@ -1,5 +1,6 @@ import os import re + from deploy.logger import logger BASE_FOLDER = './deploy/AidLux' @@ -60,5 +61,6 @@ def aidlux_requirements_generate(requirements_in='requirements-in.txt'): write_file(os.path.join(BASE_FOLDER, f'./{aidlux}/requirements.txt'), data=new) + if __name__ == "__main__": - aidlux_requirements_generate() \ No newline at end of file + aidlux_requirements_generate() diff --git a/deploy/docker/requirements.txt b/deploy/docker/requirements.txt index 896a6a18d..348a1f899 100644 --- a/deploy/docker/requirements.txt +++ b/deploy/docker/requirements.txt @@ -27,4 +27,5 @@ pyzmq==22.3.0 onepush pydantic psutil==5.9.3 -av==10.0.0 \ No newline at end of file +av==10.0.0 +uiautomator2cache==0.3.0 \ No newline at end of file diff --git a/deploy/docker/requirements_generator.py b/deploy/docker/requirements_generator.py index 93a6c6d1e..aa1710ce7 100644 --- a/deploy/docker/requirements_generator.py +++ b/deploy/docker/requirements_generator.py @@ -1,9 +1,11 @@ import os + from deploy.logger import logger BASE_FOLDER = os.path.dirname(os.path.abspath(__file__)) logger.info(BASE_FOLDER) + def read_file(file): out = {} with open(file, 'r', encoding='utf-8') as f: @@ -31,11 +33,6 @@ def write_file(file, data): def docker_requirements_generate(requirements_in='requirements-in.txt'): - - if not os.path.exists(requirements_in): - requirements_in = os.path.join(BASE_FOLDER+"/../../", requirements_in) - assert os.path.exists(requirements_in) - requirements = read_file(requirements_in) logger.info(f'Generate requirements for Docker image') @@ -55,5 +52,6 @@ def docker_requirements_generate(requirements_in='requirements-in.txt'): write_file(os.path.join(BASE_FOLDER, f'./requirements.txt'), data=new) + if __name__ == '__main__': - docker_requirements_generate() \ No newline at end of file + docker_requirements_generate() diff --git a/deploy/logger.py b/deploy/logger.py index 655dcd7b0..5d487d938 100644 --- a/deploy/logger.py +++ b/deploy/logger.py @@ -1,6 +1,9 @@ import logging +import os import sys +os.chdir(os.path.join(os.path.dirname(__file__), '../')) + logger = logging.getLogger("deploy") _logger = logger @@ -14,7 +17,7 @@ logger.setLevel(logging.INFO) def hr(title, level=3): if logger is not _logger: return logger.hr(title, level) - + title = str(title).upper() if level == 0: middle = "|" + " " * 20 + title + " " * 20 + "|" @@ -30,4 +33,4 @@ def hr(title, level=3): logger.info(f"<<< {title} >>>") -logger.hr = hr \ No newline at end of file +logger.hr = hr diff --git a/requirements-in.txt b/requirements-in.txt index 0b82fe3de..e27faee35 100644 --- a/requirements-in.txt +++ b/requirements-in.txt @@ -28,4 +28,5 @@ pyzmq==22.3.0 onepush pydantic psutil==5.9.3 -av==10.0.0 \ No newline at end of file +av==10.0.0 +uiautomator2cache==0.3.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 039358fdf..9c754aa78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -80,6 +80,7 @@ tqdm==4.62.3 # via -r requirements-in.txt, gluoncv typing-extensions==4.3.0 # via asgiref, importlib-metadata, pydantic, rich, uvicorn ua-parser==0.10.0 # via user-agents uiautomator2==2.16.17 # via -r requirements-in.txt +uiautomator2cache==0.3.0 # via -r requirements-in.txt urllib3==1.22 # via requests user-agents==2.2.0 # via pywebio uvicorn[standard]==0.17.6 # via -r requirements-in.txt From 631fec2ad8dfe9b1a04cd5345a5ca2b7f3c85622 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:53:36 +0800 Subject: [PATCH 12/16] Dep: Upgrade to uiautomator2cache==0.3.0.1 --- deploy/AidLux/0.92/requirements.txt | 2 +- deploy/docker/requirements.txt | 2 +- requirements-in.txt | 2 +- requirements.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/AidLux/0.92/requirements.txt b/deploy/AidLux/0.92/requirements.txt index 99cc87806..f7c4e3ecc 100644 --- a/deploy/AidLux/0.92/requirements.txt +++ b/deploy/AidLux/0.92/requirements.txt @@ -27,4 +27,4 @@ onepush pydantic psutil==5.9.3 av==10.0.0 -uiautomator2cache==0.3.0 \ No newline at end of file +uiautomator2cache==0.3.0.1 \ No newline at end of file diff --git a/deploy/docker/requirements.txt b/deploy/docker/requirements.txt index 348a1f899..36371d6ad 100644 --- a/deploy/docker/requirements.txt +++ b/deploy/docker/requirements.txt @@ -28,4 +28,4 @@ onepush pydantic psutil==5.9.3 av==10.0.0 -uiautomator2cache==0.3.0 \ No newline at end of file +uiautomator2cache==0.3.0.1 \ No newline at end of file diff --git a/requirements-in.txt b/requirements-in.txt index e27faee35..486d5a89b 100644 --- a/requirements-in.txt +++ b/requirements-in.txt @@ -29,4 +29,4 @@ onepush pydantic psutil==5.9.3 av==10.0.0 -uiautomator2cache==0.3.0 \ No newline at end of file +uiautomator2cache==0.3.0.1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9c754aa78..1645e47d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -80,7 +80,7 @@ tqdm==4.62.3 # via -r requirements-in.txt, gluoncv typing-extensions==4.3.0 # via asgiref, importlib-metadata, pydantic, rich, uvicorn ua-parser==0.10.0 # via user-agents uiautomator2==2.16.17 # via -r requirements-in.txt -uiautomator2cache==0.3.0 # via -r requirements-in.txt +uiautomator2cache==0.3.0.1 # via -r requirements-in.txt urllib3==1.22 # via requests user-agents==2.2.0 # via pywebio uvicorn[standard]==0.17.6 # via -r requirements-in.txt From 7a16b71e080c6790538b533e4e76c1d3e9da2af6 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Sat, 1 Apr 2023 02:12:25 +0800 Subject: [PATCH 13/16] Opt: Use local assets cache in uiautomator2 --- deploy/installer.py | 4 +- deploy/patch.py | 106 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/deploy/installer.py b/deploy/installer.py index 1b1910488..805c359e4 100644 --- a/deploy/installer.py +++ b/deploy/installer.py @@ -1,6 +1,6 @@ -from deploy.patch import patch_trust_env +from deploy.patch import pre_checks -patch_trust_env() +pre_checks() from deploy.adb import AdbManager from deploy.alas import AlasManager diff --git a/deploy/patch.py b/deploy/patch.py index 4fe8480f7..8de3cbe0e 100644 --- a/deploy/patch.py +++ b/deploy/patch.py @@ -1,8 +1,10 @@ import os import re +from deploy.logger import logger -def _patch_trust_env(file): + +def patch_trust_env(file): """ People use proxies, but they never realize that proxy software leaves a global proxy pointing to itself even when the software is not running. @@ -23,15 +25,103 @@ def _patch_trust_env(file): content = re.sub('self.trust_env = True', 'self.trust_env = False', content) with open(file, 'w', encoding='utf-8') as f: f.write(content) - print(f'{file} trust_env patched') + logger.info(f'{file} trust_env patched') elif re.search('self.trust_env = False', content): - print(f'{file} trust_env already patched') + logger.info(f'{file} trust_env already patched') else: - print(f'{file} trust_env not found') + logger.info(f'{file} trust_env not found') else: - print(f'{file} trust_env no need to patch') + logger.info(f'{file} trust_env no need to patch') -def patch_trust_env(): - _patch_trust_env('./toolkit/Lib/site-packages/requests/sessions.py') - _patch_trust_env('./toolkit/Lib/site-packages/pip/_vendor/requests/sessions.py') +def check_running_directory(): + """ + An fool-proof mechanism. + Show error if user is running Easy Install in compressing software, + since Alas can't install in temp directories. + """ + file = __file__.replace(r"\\", "/").replace("\\", "/") + # C:/Users//AppData/Local/Temp/360zip$temp/360$3/AzurLaneAutoScript + if 'Temp/360zip' in file: + logger.critical('请先解压Alas的压缩包,再安装Alas') + exit(1) + # C:/Users//AppData/Local/Temp/Rar$EXa9248.23428/AzurLaneAutoScript + if 'Temp/Rar' in file or 'Local/Temp' in file: + logger.critical('Please unzip ALAS installer first') + exit(1) + + +def patch_uiautomator2(): + """ + uiautomator2 download assets from https://tool.appetizer.io first then fallback to https://github.com/openatx. + https://tool.appetizer.io is added to bypass the wall in China but https://tool.appetizer.io is slow outside of CN + plus some CN users cannot access it for unknown reason. + + So we patch `uiautomator2/init.py` to a local assets cache `uiautomator2cache/cache`. + appdir = os.path.join(os.path.expanduser('~'), '.uiautomator2') + to: + appdir = os.path.join(__file__, '../../uiautomator2cache') + + And we also remove minicap installations since emulators doesn't need it. + for url in self.minicap_urls: + self.push_url(url) + to: + for url in []: + self.push_url(url) + """ + cache_dir = './toolkit/Lib/site-packages/uiautomator2cache/cache' + init_file = './toolkit/Lib/site-packages/uiautomator2/init.py' + appdir = "os.path.join(__file__, '../../uiautomator2cache')" + + if not os.path.exists(init_file): + logger.info('uiautomator2 is not installed skip patching') + return + + modified = False + with open(init_file, 'r', encoding='utf-8') as f: + content = f.read() + + # Patch minicap_urls + res = re.search(r'self.minicap_urls', content) + if res: + content = re.sub(r'self.minicap_urls', '[]', content) + modified = True + logger.info(f'{init_file} minicap_urls patched') + else: + logger.info(f'{init_file} minicap_urls no need to patch') + + # Patch appdir + if os.path.exists(cache_dir): + res = re.search(r'appdir ?=(.*)\n', content) + if res: + prev = res.group(1).strip() + if prev == appdir: + logger.info(f'{init_file} appdir already patched') + else: + content = re.sub(r'appdir ?=.*\n', f'appdir = {appdir}\n', content) + modified = True + logger.info(f'{init_file} appdir patched') + else: + logger.info(f'{init_file} appdir not found') + else: + logger.info('uiautomator2cache is not installed skip patching') + + # Save file + if modified: + with open(init_file, 'w', encoding='utf-8') as f: + f.write(content) + logger.info(f'{init_file} content saved') + + +def pre_checks(): + check_running_directory() + + # patch_trust_env + patch_trust_env('./toolkit/Lib/site-packages/requests/sessions.py') + patch_trust_env('./toolkit/Lib/site-packages/pip/_vendor/requests/sessions.py') + + patch_uiautomator2() + + +if __name__ == '__main__': + pre_checks() From 65cfec98210803e72ce68e4e1015448074e7e53a Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:11:49 +0800 Subject: [PATCH 14/16] Fix: Remove .git/HEAD.lock --- deploy/git.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/deploy/git.py b/deploy/git.py index b3eadf5d5..28c40e58b 100644 --- a/deploy/git.py +++ b/deploy/git.py @@ -51,10 +51,13 @@ class GitManager(DeployConfig): logger.hr('Pull Repository Branch', 1) # Remove git lock - lock_file = './.git/index.lock' - if os.path.exists(lock_file): - logger.info(f'Lock file {lock_file} exists, removing') - os.remove(lock_file) + for lock_file in [ + './.git/index.lock', + './.git/HEAD.lock' + ]: + if os.path.exists(lock_file): + logger.info(f'Lock file {lock_file} exists, removing') + os.remove(lock_file) if keep_changes: if self.execute(f'"{self.git}" stash', allow_failure=True): self.execute(f'"{self.git}" pull --ff-only {source} {branch}') From 2147059858049ee717830064f39ec635cd7ab6c5 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:14:26 +0800 Subject: [PATCH 15/16] Opt: Rescan current map first perform full scan then --- module/os/map.py | 46 +++++++++++++++++++++--------------- module/os/operation_siren.py | 4 ++-- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/module/os/map.py b/module/os/map.py index a6e6d0957..cd957a24d 100644 --- a/module/os/map.py +++ b/module/os/map.py @@ -643,9 +643,11 @@ class OSMap(OSFleet, Map, GlobeCamera, StrategicSearchHandler): Args: question (bool): If clear nearing questions after auto search. - rescan (bool): Whether to rescan the whole map after running auto search. + rescan (bool, str): Whether to rescan the whole map after running auto search. This will clear siren scanning devices, siren logging tower, visit akashi's shop that auto search missed, and unlock mechanism that requires 2 fleets. + Accept str also, `current` to scan current camera only, + `full` to scan current then rescan the whole map This option should be disabled in special tasks like OpsiObscure, OpsiAbyssal, OpsiStronghold. after_auto_search (bool): @@ -653,6 +655,8 @@ class OSMap(OSFleet, Map, GlobeCamera, StrategicSearchHandler): """ if rescan is None: rescan = self.config.OpsiGeneral_DoRandomMapEvent + if rescan is True: + rescan = 'full' self.handle_ash_beacon_attack() logger.info(f'Run auto search, question={question}, rescan={rescan}') @@ -680,9 +684,9 @@ class OSMap(OSFleet, Map, GlobeCamera, StrategicSearchHandler): self._solved_map_event = set() self._solved_fleet_mechanism = False if question: - self.clear_question(drop) + self.clear_question(drop=drop) if rescan: - self.map_rescan(drop) + self.map_rescan(rescan_mode=rescan, drop=drop) if drop.count == 1: drop.clear() @@ -796,42 +800,46 @@ class OSMap(OSFleet, Map, GlobeCamera, StrategicSearchHandler): logger.info(f'No map event') return False - def map_rescan_once(self, drop=None): + def map_rescan_once(self, rescan_mode='full', drop=None): """ Args: + rescan_mode (str): `current` to scan current camera only, + `full` to scan current then rescan the whole map drop: Returns: bool: If solved a map random event """ - logger.hr('Map rescan once', level=2) - self.handle_info_bar() result = False # Try current camera first + logger.hr('Map rescan current', level=2) self.map_data_init(map_=None) + self.handle_info_bar() self.update() if self.map_rescan_current(drop=drop): logger.info(f'Map rescan once end, result={result}') return result - self.map_init(map_=None) - queue = self.map.camera_data - while len(queue) > 0: - logger.hr(f'Map rescan {queue[0]}') - queue = queue.sort_by_camera_distance(self.camera) - self.focus_to(queue[0], swipe_limit=(6, 5)) - self.focus_to_grid_center(0.25) + if rescan_mode == 'full': + logger.hr('Map rescan full', level=2) + self.map_init(map_=None) + queue = self.map.camera_data + while len(queue) > 0: + logger.hr(f'Map rescan {queue[0]}') + queue = queue.sort_by_camera_distance(self.camera) + self.focus_to(queue[0], swipe_limit=(6, 5)) + self.focus_to_grid_center(0.25) - if self.map_rescan_current(drop=drop): - result = True - break - queue = queue[1:] + if self.map_rescan_current(drop=drop): + result = True + break + queue = queue[1:] logger.info(f'Map rescan once end, result={result}') return result - def map_rescan(self, drop=None): + def map_rescan(self, rescan_mode='full', drop=None): if self.zone.is_port: logger.info('Current zone is a port, do not need rescan') return False @@ -846,7 +854,7 @@ class OSMap(OSFleet, Map, GlobeCamera, StrategicSearchHandler): logger.attr('Solved_map_event', self._solved_map_event) self.fleet_set(self.config.OpsiFleet_Fleet) return False - result = self.map_rescan_once(drop=drop) + result = self.map_rescan_once(rescan_mode=rescan_mode, drop=drop) if not result: logger.attr('Solved_map_event', self._solved_map_event) self.fleet_set(self.config.OpsiFleet_Fleet) diff --git a/module/os/operation_siren.py b/module/os/operation_siren.py index 46265c581..0c9bb61a6 100644 --- a/module/os/operation_siren.py +++ b/module/os/operation_siren.py @@ -229,7 +229,7 @@ class OperationSiren(OSMap): self.os_order_execute( recon_scan=True, submarine_call=False) - self.run_auto_search(rescan=False) + self.run_auto_search(rescan='current') self.map_exit() self.handle_after_auto_search() else: @@ -554,7 +554,7 @@ class OperationSiren(OSMap): self.os_order_execute( recon_scan=True, submarine_call=self.config.OpsiFleet_Submarine) - self.run_auto_search(rescan=False) + self.run_auto_search(rescan='current') self.map_exit() self.handle_after_auto_search() From 2f0382c46d3f31c0e4377ceb60d96c729f409ce9 Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:58:22 +0800 Subject: [PATCH 16/16] Opt: abspath in appdir of uiautomator2 cache --- deploy/patch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/patch.py b/deploy/patch.py index 8de3cbe0e..f312ba041 100644 --- a/deploy/patch.py +++ b/deploy/patch.py @@ -60,7 +60,7 @@ def patch_uiautomator2(): So we patch `uiautomator2/init.py` to a local assets cache `uiautomator2cache/cache`. appdir = os.path.join(os.path.expanduser('~'), '.uiautomator2') to: - appdir = os.path.join(__file__, '../../uiautomator2cache') + appdir = os.path.abspath(os.path.join(__file__, '../../uiautomator2cache')) And we also remove minicap installations since emulators doesn't need it. for url in self.minicap_urls: @@ -71,7 +71,7 @@ def patch_uiautomator2(): """ cache_dir = './toolkit/Lib/site-packages/uiautomator2cache/cache' init_file = './toolkit/Lib/site-packages/uiautomator2/init.py' - appdir = "os.path.join(__file__, '../../uiautomator2cache')" + appdir = "os.path.abspath(os.path.join(__file__, '../../uiautomator2cache'))" if not os.path.exists(init_file): logger.info('uiautomator2 is not installed skip patching')