1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2025-12-16 11:15:13 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
RedDeadDepresso
2a1e7686f0 fix: auto-mission 2024-01-21 21:11:34 +00:00
RedDeadDepresso
78c12a21dc refactor: auto-mission 2024-01-21 19:12:58 +00:00
36 changed files with 508 additions and 231 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -57,24 +57,25 @@
"ServerUpdate": "04:00"
},
"Formation": {
"burst1": 1,
"burst2": 4,
"pierce1": 2,
"pierce2": 4,
"mystic1": 3,
"mystic2": 4
"burst1": "1-1",
"burst2": "1-2",
"pierce1": "1-3",
"pierce2": "1-4",
"mystic1": "1-5",
"mystic2": "2-1",
"Substitute": false
},
"ManualBoss": {
"Enable": false
},
"Normal": {
"Enable": false,
"Area": 4,
"Area": null,
"Completion": "clear"
},
"Hard": {
"Enable": false,
"Area": 6,
"Area": null,
"Completion": "clear"
}
},

View File

@ -274,64 +274,32 @@
},
"Formation": {
"burst1": {
"type": "select",
"value": 1,
"option": [
1,
2,
3,
4
]
"type": "input",
"value": "1-1"
},
"burst2": {
"type": "select",
"value": 4,
"option": [
1,
2,
3,
4
]
"type": "input",
"value": "1-2"
},
"pierce1": {
"type": "select",
"value": 2,
"option": [
1,
2,
3,
4
]
"type": "input",
"value": "1-3"
},
"pierce2": {
"type": "select",
"value": 4,
"option": [
1,
2,
3,
4
]
"type": "input",
"value": "1-4"
},
"mystic1": {
"type": "select",
"value": 3,
"option": [
1,
2,
3,
4
]
"type": "input",
"value": "1-5"
},
"mystic2": {
"type": "select",
"value": 4,
"option": [
1,
2,
3,
4
]
"type": "input",
"value": "2-1"
},
"Substitute": {
"type": "checkbox",
"value": false
}
},
"ManualBoss": {
@ -346,8 +314,8 @@
"value": false
},
"Area": {
"type": "input",
"value": 4
"type": "textarea",
"value": null
},
"Completion": {
"type": "select",
@ -364,8 +332,8 @@
"value": false
},
"Area": {
"type": "input",
"value": 6
"type": "textarea",
"value": null
},
"Completion": {
"type": "select",

View File

@ -243,38 +243,31 @@ TacticalChallengeShop:
"15": false
Formation:
burst1:
value: 1
option: [ 1, 2, 3, 4 ]
burst2:
value: 4
option: [ 1, 2, 3, 4 ]
pierce1:
value: 2
option: [ 1, 2, 3, 4 ]
pierce2:
value: 4
option: [ 1, 2, 3, 4 ]
mystic1:
value: 3
option: [ 1, 2, 3, 4 ]
mystic2:
value: 4
option: [ 1, 2, 3, 4 ]
burst1: 1-1
burst2: 1-2
pierce1: 1-3
pierce2: 1-4
mystic1: 1-5
mystic2: 2-1
Substitute: false
ManualBoss:
Enable: false
Normal:
Enable: false
Area: 4
Area:
value: null
type: textarea
Completion:
value: clear
option: [ clear, three_stars]
Hard:
Enable: false
Area: 6
Area:
value: null
type: textarea
Completion:
value: clear
option: [ clear, three_stars, three_stars_chest]

View File

@ -160,24 +160,25 @@ class GeneratedConfig:
TacticalChallengeShop_15 = False
# Group `Formation`
Formation_burst1 = 1 # 1, 2, 3, 4
Formation_burst2 = 4 # 1, 2, 3, 4
Formation_pierce1 = 2 # 1, 2, 3, 4
Formation_pierce2 = 4 # 1, 2, 3, 4
Formation_mystic1 = 3 # 1, 2, 3, 4
Formation_mystic2 = 4 # 1, 2, 3, 4
Formation_burst1 = '1-1'
Formation_burst2 = '1-2'
Formation_pierce1 = '1-3'
Formation_pierce2 = '1-4'
Formation_mystic1 = '1-5'
Formation_mystic2 = '2-1'
Formation_Substitute = False
# Group `ManualBoss`
ManualBoss_Enable = False
# Group `Normal`
Normal_Enable = False
Normal_Area = 4
Normal_Area = None
Normal_Completion = 'clear' # clear, three_stars
# Group `Hard`
Hard_Enable = False
Hard_Area = 6
Hard_Area = None
Hard_Completion = 'clear' # clear, three_stars, three_stars_chest
# Group `ItemStorage`

View File

@ -827,55 +827,35 @@
"Formation": {
"_info": {
"name": "Formation",
"help": "Select the unit for each type"
"help": "AAS will choose the unit from the Preset List in the Layout after entering the Unit Formation Page. The format is preset-row, for example 1-1 means choose preset 1 row 1. Please make sure the presets are set to their original names (1,2,3,4). It is highly recommended that you set them up to be unique."
},
"burst1": {
"name": "Explosive 1",
"help": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": ""
},
"burst2": {
"name": "Explosive 2",
"help": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": ""
},
"pierce1": {
"name": "Piercing 1",
"help": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": ""
},
"pierce2": {
"name": "Piercing 2",
"help": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": ""
},
"mystic1": {
"name": "Mystic 1",
"help": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": ""
},
"mystic2": {
"name": "Mystic 2",
"help": "",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": ""
},
"Substitute": {
"name": "Find Alternatives",
"help": "In case you set the preset for some types to be the same and the stage requires all of those types, AAS will try to find alternatives. For example, if you set Explosive 1 and Explosive 2 to be the same and the stage requires both of them, AAS will replace Explosive 2 with Piercing 1."
}
},
"ManualBoss": {
@ -899,7 +879,7 @@
},
"Area": {
"name": "Area",
"help": ""
"help": "Currently only between 4 and 16. Use > to connect multiple areas. Example: 6 > 7 > 8"
},
"Completion": {
"name": "Completion level",
@ -919,7 +899,7 @@
},
"Area": {
"name": "Area",
"help": ""
"help": "Currently only between 6 and 16. Use > to connect multiple areas. Example: 6 > 7 > 8"
},
"Completion": {
"name": "Completion level",

View File

@ -831,51 +831,31 @@
},
"burst1": {
"name": "Formation.burst1.name",
"help": "Formation.burst1.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": "Formation.burst1.help"
},
"burst2": {
"name": "Formation.burst2.name",
"help": "Formation.burst2.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": "Formation.burst2.help"
},
"pierce1": {
"name": "Formation.pierce1.name",
"help": "Formation.pierce1.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": "Formation.pierce1.help"
},
"pierce2": {
"name": "Formation.pierce2.name",
"help": "Formation.pierce2.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": "Formation.pierce2.help"
},
"mystic1": {
"name": "Formation.mystic1.name",
"help": "Formation.mystic1.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": "Formation.mystic1.help"
},
"mystic2": {
"name": "Formation.mystic2.name",
"help": "Formation.mystic2.help",
"1": "1",
"2": "2",
"3": "3",
"4": "4"
"help": "Formation.mystic2.help"
},
"Substitute": {
"name": "Formation.Substitute.name",
"help": "Formation.Substitute.help"
}
},
"ManualBoss": {

View File

@ -13,8 +13,6 @@ class ScrollSelect:
Parameters
----------
targetind : int
Index of the target level
window_starty:
Y-coordinate of the upper edge of the window
first_item_endy:
@ -31,7 +29,7 @@ class ScrollSelect:
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,
def __init__(self, window_button, first_item_button, expected_button, clickx, swipeoffsetx=-100, responsey=40,
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]
@ -42,7 +40,7 @@ class ScrollSelect:
self.clickx = clickx
self.expected_button = expected_button
self.swipeoffsetx = swipeoffsetx
self.responsey = 40
self.responsey = responsey
self.finalclick = finalclick
def compute_swipe(self, main, x1, y1, distance, responsey):
@ -59,7 +57,7 @@ class ScrollSelect:
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:
def select_index(self, main, target_index, clickoffsety=0) -> 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)
@ -110,8 +108,10 @@ class ScrollSelect:
self.responsey)
if self.finalclick:
# Click on the last row
clicky = (self.window_endy - self.itemheight // 2) + clickoffsety
logger.info(clicky)
self.run_until(main,
lambda: click_coords(self.clickx, self.window_endy - self.itemheight // 2),
lambda: click_coords(self.clickx, clicky),
lambda: main.appear(self.expected_button)
)

View File

@ -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,180 @@ ONE_STAR = ButtonWrapper(
button=(148, 349, 183, 390),
),
)
PRESET1_OFF = ButtonWrapper(
name='PRESET1_OFF',
jp=None,
en=None,
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=None,
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=None,
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=None,
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,
@ -345,6 +537,24 @@ RANK = ButtonWrapper(
button=(540, 628, 738, 689),
),
)
RECEIVED_CHEST = ButtonWrapper(
name='RECEIVED_CHEST',
jp=None,
en=Button(
file='./assets/en/auto_mission/RECEIVED_CHEST.png',
area=(502, 188, 774, 226),
search=(482, 168, 794, 246),
color=(198, 208, 217),
button=(547, 487, 738, 547),
),
zht=Button(
file='./assets/zht/auto_mission/RECEIVED_CHEST.png',
area=(569, 192, 704, 226),
search=(549, 172, 724, 246),
color=(177, 187, 197),
button=(549, 488, 736, 543),
),
)
REWARD_ACQUIRED = ButtonWrapper(
name='REWARD_ACQUIRED',
jp=None,

View File

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

View File

@ -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 = [85, 85, 0, -120, 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):
@ -122,7 +146,20 @@ class Copilot(UI):
force_index = self.get_force()
self.sleep(1)
return force_index
def handle_all_mission_popup(self):
self.sleep(2)
while not self.match_color(MISSION_INFO):
self.device.screenshot()
if self.match_color(MISSION_INFO):
break
if self.appear_then_click(MISSION_INFO_POPUP):
continue
if self.appear_then_click(MOVE_UNIT):
continue
if self.appear_then_click(RECEIVED_CHEST):
continue
def handle_mission_popup(self, button, skip_first_screenshot=True):
while 1:
if skip_first_screenshot:
@ -146,9 +183,10 @@ class Copilot(UI):
while 1:
self.device.screenshot()
if not self.match_color(END_PHASE):
self.handle_mission_popup(END_PHASE_POPUP)
self.handle_all_mission_popup()
break
self.appear_then_click(END_PHASE)
self.sleep(2)
def wait_over(self):
#self.sleep(2)
@ -220,6 +258,8 @@ class Copilot(UI):
if 'wait-over' in act:
self.wait_over()
self.sleep(2)
if i != len(actions) - 1:
self.handle_all_mission_popup()
logger.warning("Actions completed, waiting to enter the battle...")

View File

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

View File

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