1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2025-12-16 19:55:12 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
RedDeadDepresso
0407762071
Merge 1a648152a6 into a5d478ce56 2024-01-22 22:21:51 +00:00
RedDeadDepresso
1a648152a6 style: comments in auto-mission 2024-01-22 19:33:14 +00:00
4 changed files with 88 additions and 74 deletions

View File

@ -1,5 +1,6 @@
from tasks.mission.mission import Mission from tasks.mission.mission import Mission
from tasks.mission.ui import SWITCH_NORMAL, SWITCH_HARD from tasks.mission.ui import SWITCH_NORMAL, SWITCH_HARD
from tasks.auto_mission.stage import Stage
from tasks.auto_mission.ui import AutoMissionUI from tasks.auto_mission.ui import AutoMissionUI
from enum import Enum from enum import Enum
@ -12,29 +13,33 @@ import re
class AutoMissionStatus(Enum): class AutoMissionStatus(Enum):
AP = 0 # Calculate AP and decide to terminate Auto-Mission module or not AP = 0 # Calculate AP and decide to terminate Auto-Mission module or not
STAGES_DATA = 1 STAGES_DATA = 1 # Retrieve stages data for the area and resolve conflicts for type_to_preset
NAVIGATE = 2 # Navigate to the area and select mode NAVIGATE = 2 # Navigate to the area and select mode
ENTER = 3 # Enter the first stage in the stage list ENTER = 3 # Enter the first stage in the stage list
CHECK = 4 # Check stages and find a stage that requires to be completed CHECK = 4 # Check stages and find a stage that requires to be completed
START = 5 # Start the stage START = 5 # Start the stage
FORMATION = 6 # Select units based on the types required by the stage FORMATION = 6 # Select units based on the types required by the stage
FIGHT = 7 # Fight the stage FIGHT = 7 # Fight the stage
END = 8 END = 8 # Update task
FINISH = -1 # Indicate termination of Auto-Mission module FINISH = -1 # Indicate termination of Auto-Mission module
class AutoMission(AutoMissionUI, Mission): class AutoMission(AutoMissionUI, Mission):
def __init__(self, config, device): def __init__(self, config, device):
super().__init__(config, device) super().__init__(config, device)
self.task = None self.task: list[str, list[int], bool] = None
self.previous_mode = None self.previous_mode: str = None
self.previous_area = None self.previous_area: int = None
self._stage = None self._stage: Stage = None
self.stages_data = None self.stages_data: dict = None
self.default_type_to_preset = self.get_default_type_to_preset() self.default_type_to_preset: dict = self.get_default_type_to_preset()
self.current_type_to_preset = None self.current_type_to_preset: dict = None
def get_default_type_to_preset(self): def get_default_type_to_preset(self) -> dict[str, list[int, int]]:
type_to_preset = { """
Validate preset settings and returs a dictionary
mapping each type to its preset e.g {burst1: [1, 1]}
"""
type_to_preset: dict[str, str] = {
"burst1": self.config.Formation_burst1, "burst1": self.config.Formation_burst1,
"burst2": self.config.Formation_burst2, "burst2": self.config.Formation_burst2,
"pierce1": self.config.Formation_pierce1, "pierce1": self.config.Formation_pierce1,
@ -43,7 +48,6 @@ class AutoMission(AutoMissionUI, Mission):
"mystic2": self.config.Formation_mystic2 "mystic2": self.config.Formation_mystic2
} }
valid = True valid = True
for type, preset in type_to_preset.items(): for type, preset in type_to_preset.items():
preset_list = [] preset_list = []
if isinstance(preset, str): if isinstance(preset, str):
@ -64,8 +68,11 @@ class AutoMission(AutoMissionUI, Mission):
raise RequestHumanTakeover raise RequestHumanTakeover
return type_to_preset return type_to_preset
def validate_area(self, mode, area_input): def validate_area(self, mode, area_input) -> list[int]:
area_list = [] """
Validate the area input and returns the area as a list of integers
"""
area_list: list[int] = []
if isinstance(area_input, str): if isinstance(area_input, str):
area_input = re.sub(r'[ \t\r\n]', '', area_input) area_input = re.sub(r'[ \t\r\n]', '', area_input)
area_input = (re.sub(r'[>﹥›˃ᐳ❯]', '>', area_input)).split('>') area_input = (re.sub(r'[>﹥›˃ᐳ❯]', '>', area_input)).split('>')
@ -75,16 +82,16 @@ class AutoMission(AutoMissionUI, Mission):
area_list = [str(area_input)] area_list = [str(area_input)]
if area_list and len([x for x in area_list if x.isdigit()]) == len(area_list): if area_list and len([x for x in area_list if x.isdigit()]) == len(area_list):
return area_list return [int(x) for x in area_list]
else:
mode_name = "Normal" if mode == "N" else "H" mode_name = "Normal" if mode == "N" else "H"
logger.error(f"Failed to read Mission {mode_name}'s area settings") logger.error(f"Failed to read Mission {mode_name}'s area settings")
return None return None
def find_alternative(self, type, preset_list): def find_alternative(self, type: str, preset_list: list[list[int, int]]) -> list[list[int, int]]:
if not self.config.Formation_Substitute: if not self.config.Formation_Substitute:
return None return None
alternatives_dictionary = { alternatives_dictionary = {
'pierce1': ['pierce2', 'burst1', 'burst2', 'mystic1', 'mystic2'], 'pierce1': ['pierce2', 'burst1', 'burst2', 'mystic1', 'mystic2'],
'pierce2': ['burst1', 'burst2', 'mystic1', 'mystic2'], 'pierce2': ['burst1', 'burst2', 'mystic1', 'mystic2'],
@ -104,17 +111,23 @@ class AutoMission(AutoMissionUI, Mission):
return None return None
@property @property
def mission_info(self) -> list: def mission_info(self) -> list[str, list[int], bool]:
"""
Generate task, a list of list where each inner list is defined as
[mode, area_list, completion_level] e.g ["H", [6,7,8], "clear"]
"""
valid = True valid = True
mode = ("N", "H") mode = ("N", "H")
enable = (self.config.Normal_Enable, self.config.Hard_Enable) enable: tuple[bool] = (self.config.Normal_Enable, self.config.Hard_Enable)
area = (self.config.Normal_Area, self.config.Hard_Area) area: tuple[str] = (self.config.Normal_Area, self.config.Hard_Area)
area_list = [None, None] area_list: list[list[int]] = [None, None]
completion_level = (self.config.Normal_Completion, self.config.Hard_Completion) completion_level: tuple[bool] = (self.config.Normal_Completion, self.config.Hard_Completion)
for index in range(2): for index in range(2):
if enable[index]: if enable[index]:
area_list[index] = self.validate_area(mode[index], area[index]) area_list[index] = self.validate_area(mode[index], area[index])
valid = valid if area_list[index] else False valid = valid if area_list[index] else False
if valid: if valid:
info = zip(mode, area_list, completion_level) info = zip(mode, area_list, completion_level)
return list(filter(lambda x: x[1], info)) return list(filter(lambda x: x[1], info))
@ -126,44 +139,46 @@ class AutoMission(AutoMissionUI, Mission):
@property @property
def current_area(self): def current_area(self):
return int(self.task[0][1][0]) return self.task[0][1][0]
@property @property
def current_stage(self): def current_stage(self):
return self._stage return self._stage
@current_stage.setter @current_stage.setter
def current_stage(self, value): def current_stage(self, value: Stage):
self._stage = value self._stage = value
@property @property
def current_completion_level(self): def current_completion_level(self):
return self.task[0][2] return self.task[0][2]
@property @property
def current_count(self): def current_count(self):
return 1 # required to use update_ap() and get_realistic_count()
return 1
def update_stages_data(self): def update_stages_data(self) -> bool:
if [self.previous_mode, self.previous_area] != [self.current_mode, self.current_area]: if [self.previous_mode, self.previous_area] != [self.current_mode, self.current_area]:
self.stages_data = self.get_stages_data(self.current_mode, self.current_area) self.stages_data = self.get_stages_data(self.current_mode, self.current_area)
if self.stages_data: if self.stages_data:
return True return True
return False return False
def update_current_type_to_preset(self): def update_current_type_to_preset(self) -> bool:
if [self.previous_mode, self.previous_area] == [self.current_mode, self.current_area]: if [self.previous_mode, self.previous_area] == [self.current_mode, self.current_area]:
# set it to None. This will skip changing preset in self.formation
self.current_type_to_preset = None self.current_type_to_preset = None
return True return True
mode_name = "Normal" if self.current_mode == "N" else "Hard" mode_name = "Normal" if self.current_mode == "N" else "Hard"
use_alternative = False use_alternative = False
for stage, info in self.stages_data.items(): for stage, info in self.stages_data.items():
if "start" not in info: if "start" not in info:
continue continue
list_preset = [] list_preset: list[list[int, int]] = []
list_type = [] list_type : list[str] = []
for type in info["start"]: for type in info["start"]:
preset = self.default_type_to_preset[type] preset = self.default_type_to_preset[type]
list_type.append(type) list_type.append(type)
@ -171,7 +186,8 @@ class AutoMission(AutoMissionUI, Mission):
if preset not in list_preset: if preset not in list_preset:
list_preset.append(preset) list_preset.append(preset)
continue continue
logger.error(f"Mission {mode_name} {self.current_area} requires {list_type} but they are both set to preset {preset}") logger.error(f"Mission {mode_name} {self.current_area} requires\
{list_type} but they are both set to preset {preset}")
list_preset = self.find_alternative(type, list_preset) list_preset = self.find_alternative(type, list_preset)
use_alternative = True use_alternative = True
if list_preset: if list_preset:
@ -179,11 +195,11 @@ class AutoMission(AutoMissionUI, Mission):
return False return False
if use_alternative: if use_alternative:
d = {} alt_type_to_preset: dict[str, list[list[int, int]]] = {}
for index in range(len(list_type)): for index in range(len(list_type)):
type, preset = list_type[index], list_preset[index] type, preset = list_type[index], list_preset[index]
d[type] = preset alt_type_to_preset[type] = preset
self.current_type_to_preset = d self.current_type_to_preset = alt_type_to_preset
else: else:
self.current_type_to_preset = self.default_type_to_preset self.current_type_to_preset = self.default_type_to_preset
return True return True
@ -225,7 +241,9 @@ class AutoMission(AutoMissionUI, Mission):
raise RequestHumanTakeover raise RequestHumanTakeover
case AutoMissionStatus.CHECK: case AutoMissionStatus.CHECK:
self.current_stage = self.check_stages(self.current_mode, self.current_area, self.stages_data, self.current_completion_level) self.current_stage: Stage = self.check_stages(
self.current_mode, self.current_area, self.stages_data, self.current_completion_level
)
if self.current_stage: if self.current_stage:
return AutoMissionStatus.START return AutoMissionStatus.START
return AutoMissionStatus.END return AutoMissionStatus.END

View File

@ -5,7 +5,7 @@ from module.ocr.ocr import Digit
from tasks.base.ui import UI from tasks.base.ui import UI
from tasks.base.assets.assets_base_page import MISSION_CHECK from tasks.base.assets.assets_base_page import MISSION_CHECK
from tasks.auto_mission.assets.assets_auto_mission import * from tasks.auto_mission.assets.assets_auto_mission import *
from tasks.auto_mission.stage import StageState from tasks.auto_mission.stage import StageState, Stage
PRESETS = [PRESET1_ON, PRESET2_ON, PRESET3_ON, PRESET4_ON] PRESETS = [PRESET1_ON, PRESET2_ON, PRESET3_ON, PRESET4_ON]
@ -41,8 +41,8 @@ class Copilot(UI):
super().__init__(config, device) super().__init__(config, device)
self.ocr_unit = Digit(OCR_UNIT) self.ocr_unit = Digit(OCR_UNIT)
"""Utility methods""" """---------------------- UTILITY METHODS ------------------------"""
def sleep(self, num): def sleep(self, num: int):
timer = Timer(num).start() timer = Timer(num).start()
while not timer.reached(): while not timer.reached():
pass pass
@ -53,7 +53,7 @@ class Copilot(UI):
# sleep because clicks can be too fast when executing actions # sleep because clicks can be too fast when executing actions
self.sleep(interval) self.sleep(interval)
def click_then_check(self, coords, dest_check): def click_then_check(self, coords: tuple[int, int], dest_check: ButtonWrapper):
while 1: while 1:
self.device.screenshot() self.device.screenshot()
if self.appear(dest_check): if self.appear(dest_check):
@ -68,7 +68,7 @@ class Copilot(UI):
return True return True
self.sleep(2) self.sleep(2)
def set_switch(self, switch): def set_switch(self, switch: Switch):
""" """
Set skip switch to on Set skip switch to on
Returns: Returns:
@ -82,13 +82,11 @@ class Copilot(UI):
switch.set('on', main=self) switch.set('on', main=self)
return True return True
"""Formation methods""" """---------------------- FORMATION METHODS ------------------------"""
def choose_from_preset(self, type, type_to_preset): def choose_from_preset(self, type: str, type_to_preset: dict):
preset, row = type_to_preset[type] preset, row = type_to_preset[type]
preset_index = preset - 1 preset_index = preset - 1
row_index = row - 1
self.select_then_check(LAYOUT, PRESET_LIST) self.select_then_check(LAYOUT, PRESET_LIST)
#self.set_switch(PRESET_SWITCHES[preset_index])
PRESET = PRESETS[preset_index] PRESET = PRESETS[preset_index]
while not self.match_color(PRESET, threshold=50): while not self.match_color(PRESET, threshold=50):
self.device.screenshot() self.device.screenshot()
@ -121,15 +119,15 @@ class Copilot(UI):
wait() wait()
click_second() click_second()
def choose_unit(self, unit): def choose_unit(self, unit: int):
unit_index = unit - 1 unit_index = unit - 1
unit_switch = UNIT_SWITCHES[unit_index] unit_switch = UNIT_SWITCHES[unit_index]
self.set_switch(unit_switch) self.set_switch(unit_switch)
def goto_formation_page(self, start_coords): def goto_formation_page(self, start_coords: tuple[int, int]):
self.click_then_check(start_coords, MOBILIZE) self.click_then_check(start_coords, MOBILIZE)
def formation(self, stage, type_to_preset): def formation(self, stage: Stage, type_to_preset: dict):
if stage.state == StageState.SUB: if stage.state == StageState.SUB:
# Select a unit to start the battle # Select a unit to start the battle
self.choose_unit(1) self.choose_unit(1)
@ -147,7 +145,7 @@ class Copilot(UI):
self.select_then_check(MOBILIZE, MISSION_INFO) self.select_then_check(MOBILIZE, MISSION_INFO)
unit += 1 unit += 1
"""Fight methods""" """---------------------- FIGHT METHODS ------------------------"""
def begin_mission(self): def begin_mission(self):
# start the fight after formation. Not needed for SUB mission. # start the fight after formation. Not needed for SUB mission.
self.select_then_check(BEGIN_MISSION, END_PHASE) self.select_then_check(BEGIN_MISSION, END_PHASE)
@ -157,7 +155,7 @@ class Copilot(UI):
self.set_switch(SWITCH_SKIP_BATTLE) self.set_switch(SWITCH_SKIP_BATTLE)
self.set_switch(SWITCH_AUTO_END) self.set_switch(SWITCH_AUTO_END)
def get_force(self): def get_force(self) -> int:
# detect the current active unit in the map # detect the current active unit in the map
self.device.screenshot() self.device.screenshot()
current_unit = self.ocr_unit.ocr_single_line(self.device.image) current_unit = self.ocr_unit.ocr_single_line(self.device.image)
@ -165,7 +163,7 @@ class Copilot(UI):
return self.get_force() return self.get_force()
return current_unit return current_unit
def wait_formation_change(self, force_index): def wait_formation_change(self, force_index: int) -> int:
logger.info("Wait formation change") logger.info("Wait formation change")
origin = force_index origin = force_index
while force_index == origin: while force_index == origin:
@ -186,7 +184,7 @@ class Copilot(UI):
if self.appear_then_click(RECEIVED_CHEST): if self.appear_then_click(RECEIVED_CHEST):
continue continue
def handle_mission_popup(self, button, skip_first_screenshot=True): def handle_mission_popup(self, button: ButtonWrapper, skip_first_screenshot=True):
while 1: while 1:
if skip_first_screenshot: if skip_first_screenshot:
skip_first_screenshot = False skip_first_screenshot = False
@ -198,6 +196,7 @@ class Copilot(UI):
continue continue
def confirm_teleport(self): def confirm_teleport(self):
# Detect and confirm the end of the phase
while 1: while 1:
self.device.screenshot() self.device.screenshot()
if self.appear(MOVE_UNIT): if self.appear(MOVE_UNIT):
@ -219,7 +218,7 @@ class Copilot(UI):
self.select_then_check(MISSION_INFO, MISSION_INFO_POPUP) self.select_then_check(MISSION_INFO, MISSION_INFO_POPUP)
self.handle_mission_popup(MISSION_INFO_POPUP) self.handle_mission_popup(MISSION_INFO_POPUP)
def start_action(self, actions, manual_boss): def start_action(self, actions, manual_boss: bool):
for i, act in enumerate(actions): for i, act in enumerate(actions):
if manual_boss and i == len(actions) - 1: if manual_boss and i == len(actions) - 1:
logger.warning("Actions completed. Waiting for manual boss...") logger.warning("Actions completed. Waiting for manual boss...")
@ -332,7 +331,7 @@ class Copilot(UI):
self.device.click_record_clear() self.device.click_record_clear()
self.device.stuck_record_clear() self.device.stuck_record_clear()
def fight(self, stage, manual_boss): def fight(self, stage: Stage, manual_boss: bool):
if stage.state != StageState.SUB: if stage.state != StageState.SUB:
# Click to start the task # Click to start the task
self.begin_mission() self.begin_mission()

View File

@ -8,7 +8,7 @@ class StageState(Enum):
CHEST = 4 CHEST = 4
class Stage: class Stage:
def __init__(self, name, state, data): def __init__(self, name: str, state: str, data: dict):
self.name = name self.name = name
self.state = state self.state = state
self.data = data self.data = data

View File

@ -10,7 +10,7 @@ class AutoMissionUI(Copilot):
""" """
Class dedicated to navigate the mission page and check stages Class dedicated to navigate the mission page and check stages
""" """
def get_stages_data(self, mode, area): def get_stages_data(self, mode: str, area: int):
# Dynamically generate the complete module path # Dynamically generate the complete module path
if mode == "N": if mode == "N":
module_path = f'tasks.auto_mission.normal_task.normal_task_' + str(area) module_path = f'tasks.auto_mission.normal_task.normal_task_' + str(area)
@ -27,11 +27,9 @@ class AutoMissionUI(Copilot):
logger.error(f"Exploration not supported for Mission {mode_name} area {area}, under development...") logger.error(f"Exploration not supported for Mission {mode_name} area {area}, under development...")
return None return None
def wait_mission_info(self, mode, open_task=False, max_retry=99999): def wait_mission_info(self, mode: str, open_task=False, max_retry=99999) -> str:
""" """
Wait for the task information popup to load Wait for the mission information popup to load in the mission page
@param self:
@return:
""" """
while max_retry > 0: while max_retry > 0:
self.device.screenshot() self.device.screenshot()
@ -53,11 +51,9 @@ class AutoMissionUI(Copilot):
logger.error("max_retry {0}".format(max_retry)) logger.error("max_retry {0}".format(max_retry))
return None return None
def check_stage_state(self, mode, completion_level): def check_stage_state(self, mode: str, completion_level: str) -> StageState:
""" """
Check the current task type Check the current stage type
@param self:
@return:
""" """
# Wait for the task information popup to load # Wait for the task information popup to load
self.wait_mission_info(mode) self.wait_mission_info(mode)
@ -78,7 +74,11 @@ class AutoMissionUI(Copilot):
# Main task - Cleared # Main task - Cleared
return StageState.UNCLEARED return StageState.UNCLEARED
def get_stage_info(self, stage_name, stage_state, stages_data, completion_level): def get_stage_info(self, stage_name: str, stage_state: StageState, stages_data: dict, completion_level: str) -> dict:
"""
Retrieves the stage info from stages_data trying
to find the most suited based on completion_level
"""
possible_stages = [] possible_stages = []
for stage in stages_data: for stage in stages_data:
if stage_name in stage: if stage_name in stage:
@ -99,12 +99,9 @@ class AutoMissionUI(Copilot):
return stages_data[possible_stages[0]] return stages_data[possible_stages[0]]
return None return None
def check_stages(self, mode, area, stages_data, completion_level): def check_stages(self, mode: str, area: int, stages_data: dict, completion_level: str) -> Stage:
""" """
Find the stage that needs to be battled Find the stage that needs to be battled
@param self:
@param region:
@return:
""" """
stage_index = 1 stage_index = 1
max_index = 4 if mode == "H" else 6 max_index = 4 if mode == "H" else 6
@ -146,7 +143,7 @@ class AutoMissionUI(Copilot):
if area != Digit(OCR_AREA).ocr_single_line(self.device.image): if area != Digit(OCR_AREA).ocr_single_line(self.device.image):
return None return None
def start_stage(self, stage): def start_stage(self, stage: Stage):
# Click to start the task # Click to start the task
if stage.state == StageState.SUB: if stage.state == StageState.SUB:
self.select_then_check(ENTER_SUB, MOBILIZE) self.select_then_check(ENTER_SUB, MOBILIZE)