mirror of
https://github.com/TheFunny/ArisuAutoSweeper
synced 2026-06-24 05:14:52 +00:00
refactor: auto-mission
This commit is contained in:
@@ -183,6 +183,24 @@ ENTER_SUB = ButtonWrapper(
|
||||
button=(553, 490, 712, 538),
|
||||
),
|
||||
)
|
||||
LAYOUT = ButtonWrapper(
|
||||
name='LAYOUT',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/LAYOUT.png',
|
||||
area=(1179, 461, 1226, 504),
|
||||
search=(1159, 441, 1246, 524),
|
||||
color=(189, 198, 210),
|
||||
button=(1179, 461, 1226, 504),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/LAYOUT.png',
|
||||
area=(1179, 461, 1226, 504),
|
||||
search=(1159, 441, 1246, 524),
|
||||
color=(189, 198, 210),
|
||||
button=(1179, 461, 1226, 504),
|
||||
),
|
||||
)
|
||||
MISSION_COMPLETE = ButtonWrapper(
|
||||
name='MISSION_COMPLETE',
|
||||
jp=None,
|
||||
@@ -327,6 +345,204 @@ ONE_STAR = ButtonWrapper(
|
||||
button=(148, 349, 183, 390),
|
||||
),
|
||||
)
|
||||
PRESET1_OFF = ButtonWrapper(
|
||||
name='PRESET1_OFF',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET1_OFF.png',
|
||||
area=(52, 143, 197, 181),
|
||||
search=(32, 123, 217, 201),
|
||||
color=(253, 253, 254),
|
||||
button=(52, 143, 197, 181),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET1_OFF.png',
|
||||
area=(52, 143, 197, 181),
|
||||
search=(32, 123, 217, 201),
|
||||
color=(253, 253, 254),
|
||||
button=(52, 143, 197, 181),
|
||||
),
|
||||
)
|
||||
PRESET1_ON = ButtonWrapper(
|
||||
name='PRESET1_ON',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET1_ON.png',
|
||||
area=(54, 146, 200, 182),
|
||||
search=(34, 126, 220, 202),
|
||||
color=(48, 77, 115),
|
||||
button=(54, 146, 200, 182),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET1_ON.png',
|
||||
area=(54, 146, 200, 182),
|
||||
search=(34, 126, 220, 202),
|
||||
color=(48, 77, 115),
|
||||
button=(54, 146, 200, 182),
|
||||
),
|
||||
)
|
||||
PRESET2_OFF = ButtonWrapper(
|
||||
name='PRESET2_OFF',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET2_OFF.png',
|
||||
area=(214, 144, 355, 180),
|
||||
search=(194, 124, 375, 200),
|
||||
color=(252, 253, 253),
|
||||
button=(214, 144, 355, 180),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET2_OFF.png',
|
||||
area=(214, 144, 355, 180),
|
||||
search=(194, 124, 375, 200),
|
||||
color=(252, 253, 253),
|
||||
button=(214, 144, 355, 180),
|
||||
),
|
||||
)
|
||||
PRESET2_ON = ButtonWrapper(
|
||||
name='PRESET2_ON',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET2_ON.png',
|
||||
area=(211, 147, 355, 182),
|
||||
search=(191, 127, 375, 202),
|
||||
color=(50, 78, 116),
|
||||
button=(211, 147, 355, 182),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET2_ON.png',
|
||||
area=(211, 147, 355, 182),
|
||||
search=(191, 127, 375, 202),
|
||||
color=(50, 78, 116),
|
||||
button=(211, 147, 355, 182),
|
||||
),
|
||||
)
|
||||
PRESET3_OFF = ButtonWrapper(
|
||||
name='PRESET3_OFF',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET3_OFF.png',
|
||||
area=(369, 143, 516, 186),
|
||||
search=(349, 123, 536, 206),
|
||||
color=(251, 252, 252),
|
||||
button=(369, 143, 516, 186),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET3_OFF.png',
|
||||
area=(369, 143, 516, 186),
|
||||
search=(349, 123, 536, 206),
|
||||
color=(251, 252, 252),
|
||||
button=(369, 143, 516, 186),
|
||||
),
|
||||
)
|
||||
PRESET3_ON = ButtonWrapper(
|
||||
name='PRESET3_ON',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET3_ON.png',
|
||||
area=(365, 146, 517, 184),
|
||||
search=(345, 126, 537, 204),
|
||||
color=(49, 78, 116),
|
||||
button=(365, 146, 517, 184),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET3_ON.png',
|
||||
area=(365, 146, 517, 184),
|
||||
search=(345, 126, 537, 204),
|
||||
color=(49, 78, 116),
|
||||
button=(365, 146, 517, 184),
|
||||
),
|
||||
)
|
||||
PRESET4_OFF = ButtonWrapper(
|
||||
name='PRESET4_OFF',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET4_OFF.png',
|
||||
area=(527, 146, 675, 183),
|
||||
search=(507, 126, 695, 203),
|
||||
color=(252, 252, 253),
|
||||
button=(527, 146, 675, 183),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET4_OFF.png',
|
||||
area=(527, 146, 675, 183),
|
||||
search=(507, 126, 695, 203),
|
||||
color=(252, 252, 253),
|
||||
button=(527, 146, 675, 183),
|
||||
),
|
||||
)
|
||||
PRESET4_ON = ButtonWrapper(
|
||||
name='PRESET4_ON',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET4_ON.png',
|
||||
area=(528, 148, 676, 178),
|
||||
search=(508, 128, 696, 198),
|
||||
color=(50, 78, 117),
|
||||
button=(528, 148, 676, 178),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET4_ON.png',
|
||||
area=(528, 148, 676, 178),
|
||||
search=(508, 128, 696, 198),
|
||||
color=(50, 78, 117),
|
||||
button=(528, 148, 676, 178),
|
||||
),
|
||||
)
|
||||
PRESET_FIRST_ITEM = ButtonWrapper(
|
||||
name='PRESET_FIRST_ITEM',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET_FIRST_ITEM.png',
|
||||
area=(28, 184, 1252, 397),
|
||||
search=(8, 164, 1272, 417),
|
||||
color=(214, 224, 231),
|
||||
button=(28, 184, 1252, 397),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET_FIRST_ITEM.png',
|
||||
area=(28, 184, 1252, 397),
|
||||
search=(8, 164, 1272, 417),
|
||||
color=(214, 224, 231),
|
||||
button=(28, 184, 1252, 397),
|
||||
),
|
||||
)
|
||||
PRESET_LIST = ButtonWrapper(
|
||||
name='PRESET_LIST',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET_LIST.png',
|
||||
area=(556, 85, 726, 120),
|
||||
search=(536, 65, 746, 140),
|
||||
color=(197, 205, 213),
|
||||
button=(556, 85, 726, 120),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET_LIST.png',
|
||||
area=(567, 83, 708, 120),
|
||||
search=(547, 63, 728, 140),
|
||||
color=(177, 187, 197),
|
||||
button=(567, 83, 708, 120),
|
||||
),
|
||||
)
|
||||
PRESET_WINDOW = ButtonWrapper(
|
||||
name='PRESET_WINDOW',
|
||||
jp=None,
|
||||
en=Button(
|
||||
file='./assets/en/auto_mission/PRESET_WINDOW.png',
|
||||
area=(28, 184, 1252, 649),
|
||||
search=(8, 164, 1272, 669),
|
||||
color=(215, 226, 233),
|
||||
button=(28, 184, 1252, 649),
|
||||
),
|
||||
zht=Button(
|
||||
file='./assets/zht/auto_mission/PRESET_WINDOW.png',
|
||||
area=(28, 184, 1252, 649),
|
||||
search=(8, 164, 1272, 669),
|
||||
color=(215, 226, 233),
|
||||
button=(28, 184, 1252, 649),
|
||||
),
|
||||
)
|
||||
RANK = ButtonWrapper(
|
||||
name='RANK',
|
||||
jp=None,
|
||||
|
||||
@@ -7,60 +7,34 @@ from module.base.timer import Timer
|
||||
from module.exception import RequestHumanTakeover
|
||||
from module.logger import logger
|
||||
from tasks.item.data_update import DataUpdate
|
||||
from module.base.decorator import cached_property
|
||||
|
||||
import re
|
||||
|
||||
class AutoMissionStatus(Enum):
|
||||
AP = 0 # Calculate AP and decide to terminate Auto-Mission module or not
|
||||
NAVIGATE = 1 # Navigate to the area and select mode
|
||||
ENTER = 2 # Enter the first stage in the stage list
|
||||
CHECK = 3 # Check stages and find a stage that requires to be completed
|
||||
START = 4 # Start the stage
|
||||
FORMATION = 5 # Select units based on the types required by the stage
|
||||
FIGHT = 6 # Fight the stage
|
||||
STAGES_DATA = 1
|
||||
NAVIGATE = 2 # Navigate to the area and select mode
|
||||
ENTER = 3 # Enter the first stage in the stage list
|
||||
CHECK = 4 # Check stages and find a stage that requires to be completed
|
||||
START = 5 # Start the stage
|
||||
FORMATION = 6 # Select units based on the types required by the stage
|
||||
FIGHT = 7 # Fight the stage
|
||||
END = 8
|
||||
FINISH = -1 # Indicate termination of Auto-Mission module
|
||||
|
||||
class AutoMission(AutoMissionUI, Mission):
|
||||
@property
|
||||
def mission_info(self) -> list:
|
||||
valid = True
|
||||
mode = ("N", "H")
|
||||
enable = (self.config.Normal_Enable, self.config.Hard_Enable)
|
||||
area = (self.config.Normal_Area, self.config.Hard_Area)
|
||||
stages_data = [None, None]
|
||||
completion_level = (self.config.Normal_Completion, self.config.Hard_Completion)
|
||||
for index in range(2):
|
||||
if enable[index]:
|
||||
stages_data[index] = self.get_stages_data(mode[index], area[index])
|
||||
valid = valid if self.check_formation(mode[index], area[index], stages_data[index]) else False
|
||||
if valid:
|
||||
info = zip(mode, area, stages_data, completion_level)
|
||||
return list(filter(lambda x: x[2], info))
|
||||
|
||||
def check_formation(self, mode, area, stages_data):
|
||||
mode_name = "Normal" if mode == "N" else "Hard"
|
||||
if stages_data:
|
||||
for stage, info in stages_data.items():
|
||||
if "start" in info:
|
||||
types = info["start"]
|
||||
list_unit = []
|
||||
list_type = []
|
||||
for type in types:
|
||||
list_type.append(type)
|
||||
unit = self.type_to_unit[type]
|
||||
if unit in list_unit:
|
||||
logger.error(f"Mission {mode_name} {area} requires {list_type} but they are both set to unit {unit}")
|
||||
return False
|
||||
list_unit.append(unit)
|
||||
if list_unit and list_unit[0] > unit:
|
||||
logger.error(f"Mission {mode_name} {area} requires {list_type} but they are set to units {list_unit} respectively.\
|
||||
Due to Auto-Mission's implementation, the first unit's index must be smaller than the second unit's index.")
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
def __init__(self, config, device):
|
||||
super().__init__(config, device)
|
||||
self.task = None
|
||||
self.previous_mode = None
|
||||
self.previous_area = None
|
||||
self._stage = None
|
||||
self.stages_data = None
|
||||
self.default_type_to_preset = self.get_default_type_to_preset()
|
||||
self.current_type_to_preset = None
|
||||
|
||||
@cached_property
|
||||
def type_to_unit(self):
|
||||
return {
|
||||
def get_default_type_to_preset(self):
|
||||
type_to_preset = {
|
||||
"burst1": self.config.Formation_burst1,
|
||||
"burst2": self.config.Formation_burst2,
|
||||
"pierce1": self.config.Formation_pierce1,
|
||||
@@ -68,6 +42,83 @@ class AutoMission(AutoMissionUI, Mission):
|
||||
"mystic1": self.config.Formation_mystic1,
|
||||
"mystic2": self.config.Formation_mystic2
|
||||
}
|
||||
valid = True
|
||||
|
||||
for type, preset in type_to_preset.items():
|
||||
preset_list = []
|
||||
if isinstance(preset, str):
|
||||
preset = re.sub(r'[ \t\r\n]', '', preset)
|
||||
preset = preset.split("-")
|
||||
if len(preset) == 2:
|
||||
column = preset[0]
|
||||
row = preset[1]
|
||||
if (column.isdigit() and 1 <= int(column) <= 4) and (row.isdigit() and 1 <= int(row) <= 5):
|
||||
preset_list = [int(num) for num in preset]
|
||||
if not preset_list:
|
||||
logger.error(f"Failed to read {type}'s preset settings")
|
||||
valid = False
|
||||
continue
|
||||
type_to_preset[type] = preset_list
|
||||
|
||||
if not valid:
|
||||
raise RequestHumanTakeover
|
||||
return type_to_preset
|
||||
|
||||
def validate_area(self, mode, area_input):
|
||||
area_list = []
|
||||
if isinstance(area_input, str):
|
||||
area_input = re.sub(r'[ \t\r\n]', '', area_input)
|
||||
area_input = (re.sub(r'[>﹥›˃ᐳ❯]', '>', area_input)).split('>')
|
||||
# tried to convert to set to remove duplicates but doesn't maintain order
|
||||
[area_list.append(x) for x in area_input if x not in area_list]
|
||||
elif isinstance(area_input, int):
|
||||
area_list = [str(area_input)]
|
||||
|
||||
if area_list and len([x for x in area_list if x.isdigit()]) == len(area_list):
|
||||
return area_list
|
||||
|
||||
mode_name = "Normal" if mode == "N" else "H"
|
||||
logger.error(f"Failed to read Mission {mode_name}'s area settings")
|
||||
return None
|
||||
|
||||
def find_alternative(self, type, preset_list):
|
||||
if not self.config.Formation_Substitute:
|
||||
return None
|
||||
|
||||
alternatives_dictionary = {
|
||||
'pierce1': ['pierce2', 'burst1', 'burst2', 'mystic1', 'mystic2'],
|
||||
'pierce2': ['burst1', 'burst2', 'mystic1', 'mystic2'],
|
||||
'burst1': ['burst2', 'pierce1', 'pierce2', 'mystic1', 'mystic2'],
|
||||
'burst2': ['pierce1', 'pierce2', 'mystic1', 'mystic2'],
|
||||
'mystic1': ['mystic2', 'burst1', 'burst2', 'pierce1', 'pierce2'],
|
||||
'mystic2': ['burst1', 'burst2', 'pierce1', 'pierce2'],
|
||||
}
|
||||
alternatives = alternatives_dictionary[type]
|
||||
for alternative in alternatives:
|
||||
alternative_preset = self.default_type_to_preset[alternative]
|
||||
if alternative_preset not in preset_list:
|
||||
preset_list.append(alternative_preset)
|
||||
logger.warning(f"{type} was replaced by {alternative}")
|
||||
return preset_list
|
||||
logger.error(f"Unable to find replacements for {type}")
|
||||
return None
|
||||
|
||||
@property
|
||||
def mission_info(self) -> list:
|
||||
valid = True
|
||||
mode = ("N", "H")
|
||||
enable = (self.config.Normal_Enable, self.config.Hard_Enable)
|
||||
area = (self.config.Normal_Area, self.config.Hard_Area)
|
||||
area_list = [None, None]
|
||||
completion_level = (self.config.Normal_Completion, self.config.Hard_Completion)
|
||||
for index in range(2):
|
||||
if enable[index]:
|
||||
area_list[index] = self.validate_area(mode[index], area[index])
|
||||
valid = valid if area_list[index] else False
|
||||
if valid:
|
||||
info = zip(mode, area_list, completion_level)
|
||||
return list(filter(lambda x: x[1], info))
|
||||
return None
|
||||
|
||||
@property
|
||||
def current_mode(self):
|
||||
@@ -75,7 +126,7 @@ class AutoMission(AutoMissionUI, Mission):
|
||||
|
||||
@property
|
||||
def current_area(self):
|
||||
return self.task[0][1]
|
||||
return int(self.task[0][1][0])
|
||||
|
||||
@property
|
||||
def current_stage(self):
|
||||
@@ -85,20 +136,67 @@ class AutoMission(AutoMissionUI, Mission):
|
||||
def current_stage(self, value):
|
||||
self._stage = value
|
||||
|
||||
@property
|
||||
def current_stages_data(self):
|
||||
return self.task[0][2]
|
||||
|
||||
@property
|
||||
def current_completion_level(self):
|
||||
return self.task[0][3]
|
||||
return self.task[0][2]
|
||||
|
||||
@property
|
||||
def current_count(self):
|
||||
return 1
|
||||
|
||||
def update_stages_data(self):
|
||||
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)
|
||||
if self.stages_data:
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_current_type_to_preset(self):
|
||||
if [self.previous_mode, self.previous_area] == [self.current_mode, self.current_area]:
|
||||
self.current_type_to_preset = None
|
||||
return True
|
||||
|
||||
mode_name = "Normal" if self.current_mode == "N" else "Hard"
|
||||
use_alternative = False
|
||||
for stage, info in self.stages_data.items():
|
||||
if "start" not in info:
|
||||
continue
|
||||
|
||||
list_preset = []
|
||||
list_type = []
|
||||
for type in info["start"]:
|
||||
preset = self.default_type_to_preset[type]
|
||||
list_type.append(type)
|
||||
|
||||
if preset not in list_preset:
|
||||
list_preset.append(preset)
|
||||
continue
|
||||
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)
|
||||
use_alternative = True
|
||||
if list_preset:
|
||||
continue
|
||||
return False
|
||||
|
||||
if use_alternative:
|
||||
d = {}
|
||||
for index in range(len(list_type)):
|
||||
type, preset = list_type[index], list_preset[index]
|
||||
d[type] = preset
|
||||
self.current_type_to_preset = d
|
||||
else:
|
||||
self.current_type_to_preset = self.default_type_to_preset
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def update_task(self):
|
||||
self.task.pop(0)
|
||||
self.previous_mode = self.current_mode
|
||||
self.previous_area = self.current_area
|
||||
area_list = self.task[0][1]
|
||||
area_list.pop(0)
|
||||
if not area_list:
|
||||
self.task.pop(0)
|
||||
|
||||
def handle_auto_mission(self, status):
|
||||
match status:
|
||||
@@ -106,42 +204,49 @@ class AutoMission(AutoMissionUI, Mission):
|
||||
if self.task:
|
||||
self.realistic_count = self.get_realistic_count()
|
||||
if self.realistic_count != 0:
|
||||
return AutoMissionStatus.NAVIGATE
|
||||
return AutoMissionStatus.STAGES_DATA
|
||||
return AutoMissionStatus.FINISH
|
||||
|
||||
case AutoMissionStatus.STAGES_DATA:
|
||||
if self.update_stages_data() and self.update_current_type_to_preset():
|
||||
return AutoMissionStatus.NAVIGATE
|
||||
return AutoMissionStatus.END
|
||||
|
||||
case AutoMissionStatus.NAVIGATE:
|
||||
switch = SWITCH_NORMAL if self.current_mode == "N" else SWITCH_HARD
|
||||
self.navigate(self.previous_mode, self.current_mode)
|
||||
if self.select_area(self.current_area) and self.select_mode(switch):
|
||||
return AutoMissionStatus.ENTER
|
||||
raise RequestHumanTakeover
|
||||
|
||||
|
||||
case AutoMissionStatus.ENTER:
|
||||
if self.wait_mission_info(self.current_mode, open_task=True):
|
||||
return AutoMissionStatus.CHECK
|
||||
raise RequestHumanTakeover
|
||||
|
||||
case AutoMissionStatus.CHECK:
|
||||
self.current_stage = self.check_stages(*self.task[0])
|
||||
self.current_stage = self.check_stages(self.current_mode, self.current_area, self.stages_data, self.current_completion_level)
|
||||
if self.current_stage:
|
||||
return AutoMissionStatus.START
|
||||
self.update_task()
|
||||
return AutoMissionStatus.AP
|
||||
return AutoMissionStatus.END
|
||||
|
||||
case AutoMissionStatus.START:
|
||||
self.start_stage(self.current_stage)
|
||||
return AutoMissionStatus.FORMATION
|
||||
|
||||
case AutoMissionStatus.FORMATION:
|
||||
self.formation(self.current_stage, self.type_to_unit)
|
||||
self.formation(self.current_stage, self.current_type_to_preset)
|
||||
return AutoMissionStatus.FIGHT
|
||||
|
||||
case AutoMissionStatus.FIGHT:
|
||||
self.fight(self.current_stage, manual_boss=self.config.ManualBoss_Enable)
|
||||
# Return to the previous region to prevent map unlock card recognition
|
||||
self.select_area(self.current_area - 1)
|
||||
self.update_ap()
|
||||
self.previous_mode = self.current_mode
|
||||
self.previous_area = self.current_area
|
||||
return AutoMissionStatus.AP
|
||||
|
||||
case AutoMissionStatus.END:
|
||||
self.update_task()
|
||||
return AutoMissionStatus.AP
|
||||
|
||||
case AutoMissionStatus.FINISH:
|
||||
@@ -153,8 +258,6 @@ class AutoMission(AutoMissionUI, Mission):
|
||||
return status
|
||||
|
||||
def run(self):
|
||||
self.previous_mode = None
|
||||
self._stage = None
|
||||
self.task = self.valid_task
|
||||
if self.task:
|
||||
action_timer = Timer(0.5, 1)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.ui.switch import Switch
|
||||
from module.ui.scroll_select import ScrollSelect
|
||||
from module.ocr.ocr import Digit
|
||||
from tasks.base.ui import UI
|
||||
from tasks.base.assets.assets_base_page import MISSION_CHECK
|
||||
from tasks.auto_mission.assets.assets_auto_mission import *
|
||||
from tasks.auto_mission.stage import StageState
|
||||
|
||||
SCROLL_SELECT = ScrollSelect(PRESET_WINDOW, PRESET_FIRST_ITEM, MOBILIZE, clickx=1150, responsey=60, swipeoffsetx=-600)
|
||||
PRESETS = [PRESET1_ON, PRESET2_ON, PRESET3_ON, PRESET4_ON]
|
||||
|
||||
SWITCH_UNIT1 = Switch('Unit1_Switch')
|
||||
SWITCH_UNIT1.add_state('on', UNIT1_ON)
|
||||
SWITCH_UNIT1.add_state('off', UNIT1_OFF)
|
||||
@@ -74,8 +78,21 @@ class Copilot(UI):
|
||||
return True
|
||||
|
||||
"""Formation methods"""
|
||||
def choose_unit(self, type, type_to_unit):
|
||||
unit_index = type_to_unit[type] - 1
|
||||
def choose_from_preset(self, type, type_to_preset):
|
||||
preset, row = type_to_preset[type]
|
||||
preset_index = preset - 1
|
||||
row_index = row - 1
|
||||
self.select_then_check(LAYOUT, PRESET_LIST)
|
||||
#self.set_switch(PRESET_SWITCHES[preset_index])
|
||||
PRESET = PRESETS[preset_index]
|
||||
while not self.match_color(PRESET, threshold=50):
|
||||
self.device.screenshot()
|
||||
self.click_with_interval(PRESET, interval=1)
|
||||
clickoffsety = [90, 85, 0, -90, 0]
|
||||
SCROLL_SELECT.select_index(main=self, target_index=row_index, clickoffsety=clickoffsety[row_index])
|
||||
|
||||
def choose_unit(self, unit):
|
||||
unit_index = unit - 1
|
||||
unit_switch = UNIT_SWITCHES[unit_index]
|
||||
self.set_switch(unit_switch)
|
||||
|
||||
@@ -86,16 +103,23 @@ class Copilot(UI):
|
||||
return True
|
||||
self.click(*start_coords, interval=2)
|
||||
|
||||
def formation(self, stage, type_to_unit):
|
||||
def formation(self, stage, type_to_preset):
|
||||
if stage.state == StageState.SUB:
|
||||
# Select a unit to start the battle
|
||||
self.choose_unit(stage.formation_info, type_to_unit)
|
||||
self.choose_unit(1)
|
||||
if type_to_preset:
|
||||
type = stage.formation_info
|
||||
self.choose_from_preset(type, type_to_preset)
|
||||
self.click_with_interval(MOBILIZE, interval=1)
|
||||
else:
|
||||
unit = 1
|
||||
for type, start_coords in stage.formation_start_info:
|
||||
self.goto_formation_page(start_coords)
|
||||
self.choose_unit(type, type_to_unit)
|
||||
self.choose_unit(unit)
|
||||
if type_to_preset:
|
||||
self.choose_from_preset(type, type_to_preset)
|
||||
self.select_then_check(MOBILIZE, MISSION_INFO)
|
||||
unit += 1
|
||||
|
||||
"""Fight methods"""
|
||||
def begin_mission(self):
|
||||
|
||||
@@ -23,7 +23,8 @@ class AutoMissionUI(Copilot):
|
||||
# Get stage_data data from the module
|
||||
return stage_data
|
||||
except ModuleNotFoundError:
|
||||
logger.error(f"Exploration not supported for area {area}, under development...")
|
||||
mode_name = "Normal" if mode == "N" else "Hard"
|
||||
logger.error(f"Exploration not supported for Mission {mode_name} area {area}, under development...")
|
||||
return None
|
||||
|
||||
def wait_mission_info(self, mode, open_task=False, max_retry=99999):
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
"""
|
||||
Original Author: sanmusen214(https://github.com/sanmusen214)
|
||||
Adapted from https://github.com/sanmusen214/BAAH/blob/1.2/modules/AllTask/SubTask/ScrollSelect.py
|
||||
"""
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
|
||||
|
||||
class ScrollSelect:
|
||||
"""
|
||||
Scroll and select the corresponding level by clicking on the right-side window.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
targetind : int
|
||||
Index of the target level
|
||||
window_starty:
|
||||
Y-coordinate of the upper edge of the window
|
||||
first_item_endy:
|
||||
Y-coordinate of the lower edge of the first item
|
||||
window_endy:
|
||||
Y-coordinate of the lower edge of the window
|
||||
clickx: int
|
||||
Base X-coordinate for sliding and clicking the button
|
||||
hasexpectimage: function
|
||||
Function to determine the appearance of the expected image after clicking, returns a boolean
|
||||
swipeoffsetx: int
|
||||
X offset of the base X-coordinate during sliding to prevent accidental button clicks
|
||||
finalclick: bool
|
||||
Whether to click on clickx and the last row after the sliding ends
|
||||
"""
|
||||
|
||||
def __init__(self, window_button, first_item_button, expected_button, clickx, swipeoffsetx=-100,
|
||||
finalclick=True) -> None:
|
||||
# TODO: Actually, only concerned about the height of one element, completely displaying the Y of the first button, completely displaying the Y of the bottom button, the number of complete elements that the window can contain, the height of the last element in the window, and the left offset and response distance.
|
||||
self.window_starty = window_button.area[1]
|
||||
self.window_endy = window_button.area[3]
|
||||
self.first_item_endy = first_item_button.area[3]
|
||||
self.windowheight = window_button.height
|
||||
self.itemheight = first_item_button.height
|
||||
self.clickx = clickx
|
||||
self.expected_button = expected_button
|
||||
self.swipeoffsetx = swipeoffsetx
|
||||
self.responsey = 40
|
||||
self.finalclick = finalclick
|
||||
|
||||
def compute_swipe(self, main, x1, y1, distance, responsey):
|
||||
"""
|
||||
Swipe vertically from bottom to top, actual swipe distance calculated based on the distance between two target points, considering inertia.
|
||||
"""
|
||||
distance = abs(distance)
|
||||
logger.info(f"Swipe distance: {distance}")
|
||||
# 0-50
|
||||
if distance < 50:
|
||||
main.device.swipe((x1, y1), (x1, y1 - (distance + responsey)), duration=2)
|
||||
else:
|
||||
# Effective swipe distance for the Chinese server is 60
|
||||
main.device.swipe((x1, y1), (x1, int(y1 - (distance + responsey - 4 * (1 + distance / 100)))),
|
||||
duration=1 + distance / 100)
|
||||
|
||||
def select_location(self, main, target_index) -> None:
|
||||
click_coords = main.device.click_methods.get(main.config.Emulator_ControlMethod, main.device.click_adb)
|
||||
logger.info("Scroll and select the {}-th level".format(target_index + 1))
|
||||
self.scroll_right_up(main, scrollx=self.clickx + self.swipeoffsetx)
|
||||
# Calculate how many complete elements are on one page
|
||||
itemcount = self.windowheight // self.itemheight
|
||||
# Calculate how much height the last incomplete element on this page occupies
|
||||
lastitemheight = self.windowheight % self.itemheight
|
||||
# Height below the incomplete element
|
||||
hiddenlastitemheight = self.itemheight - lastitemheight
|
||||
# Center point of the height of the first element
|
||||
start_center_y = self.window_starty + self.itemheight // 2
|
||||
# Center point of the last complete element on this page
|
||||
end_center_y = start_center_y + (itemcount - 1) * self.itemheight
|
||||
# If the target element is on the current page
|
||||
if target_index < itemcount:
|
||||
# Center point of the target element
|
||||
target_center_y = start_center_y + self.itemheight * target_index
|
||||
self.run_until(main,
|
||||
lambda: click_coords(self.clickx, target_center_y),
|
||||
lambda: main.appear(self.expected_button),
|
||||
)
|
||||
else:
|
||||
# Start scrolling from the gap in the middle of the levels
|
||||
scroll_start_from_y = self.window_endy - self.itemheight // 2
|
||||
# The target element is on subsequent pages
|
||||
# Calculate how much the page should be scrolled
|
||||
scrolltotal_distance = (target_index - itemcount) * self.itemheight + hiddenlastitemheight
|
||||
logger.info("Height hidden by the last element: %d" % hiddenlastitemheight)
|
||||
# First, slide up the hidden part, add a little distance to let the system recognize it as a swipe event
|
||||
self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, hiddenlastitemheight,
|
||||
self.responsey)
|
||||
logger.info(f"Swipe distance: {hiddenlastitemheight}")
|
||||
# Update scrolltotal_distance
|
||||
scrolltotal_distance -= hiddenlastitemheight
|
||||
# Still need to scroll up (target_index - itemcount) * self.itemheight
|
||||
# Important: slide the height of (itemcount - 1) elements each time
|
||||
if itemcount == 1:
|
||||
scroll_distance = itemcount * self.itemheight
|
||||
else:
|
||||
scroll_distance = (itemcount - 1) * self.itemheight
|
||||
while scroll_distance <= scrolltotal_distance:
|
||||
self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, scroll_distance,
|
||||
self.responsey)
|
||||
scrolltotal_distance -= scroll_distance
|
||||
if scrolltotal_distance > 5:
|
||||
# Last slide
|
||||
self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, scrolltotal_distance,
|
||||
self.responsey)
|
||||
if self.finalclick:
|
||||
# Click on the last row
|
||||
self.run_until(main,
|
||||
lambda: click_coords(self.clickx, self.window_endy - self.itemheight // 2),
|
||||
lambda: main.appear(self.expected_button)
|
||||
)
|
||||
|
||||
def run_until(self, main, func1, func2, times=6, sleeptime=1.5) -> bool:
|
||||
"""
|
||||
Repeat the execution of func1 up to a maximum of times or until func2 evaluates to True.
|
||||
|
||||
func1 should perform a single valid operation or internally call a screenshot function.
|
||||
A screenshot is triggered before evaluating func2.
|
||||
|
||||
After each execution of func1, wait for sleeptime seconds.
|
||||
|
||||
If func2 evaluates to True, exit and return True. Otherwise, return False.
|
||||
|
||||
Note: The comment assumes that func1 produces a meaningful operation or internally calls a screenshot function,
|
||||
and func2 is evaluated after each execution of func1.
|
||||
"""
|
||||
for i in range(times):
|
||||
main.device.screenshot()
|
||||
if func2():
|
||||
return True
|
||||
func1()
|
||||
timer = Timer(sleeptime).start()
|
||||
while not timer.reached_and_reset():
|
||||
pass
|
||||
main.device.screenshot()
|
||||
if func2():
|
||||
return True
|
||||
logger.warning("run_until exceeded max times")
|
||||
return False
|
||||
|
||||
def scroll_right_up(self, main, scrollx=928, times=3):
|
||||
"""
|
||||
scroll to top
|
||||
"""
|
||||
for i in range(times):
|
||||
main.device.swipe((scrollx, 226), (scrollx, 561), duration=0.2)
|
||||
timer = Timer(0.5).start()
|
||||
while not timer.reached_and_reset():
|
||||
pass
|
||||
@@ -4,10 +4,10 @@ from module.base.decorator import Config
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import DigitCounter
|
||||
from module.ui.scroll_select import ScrollSelect
|
||||
from tasks.base.assets.assets_base_page import SCHEDULE_CHECK
|
||||
from tasks.base.ui import UI
|
||||
from tasks.schedule.assets.assets_schedule import *
|
||||
from tasks.schedule.scroll_select import ScrollSelect
|
||||
|
||||
SCROLL_SELECT = ScrollSelect(window_button=SCROLL, first_item_button=FIRST_ITEM, expected_button=LOCATIONS, clickx=1116)
|
||||
xs = np.linspace(299, 995, 3, dtype=int)
|
||||
@@ -51,7 +51,7 @@ class ScheduleUI(UI):
|
||||
return False
|
||||
|
||||
def enter_location(self, location):
|
||||
SCROLL_SELECT.select_location(self, location)
|
||||
SCROLL_SELECT.select_index(main=self, target_index=location)
|
||||
if not self.appear(LOCATIONS):
|
||||
logger.error("Unable to navigate to page for location {}".format(location + 1))
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user