diff --git a/assets/en/auto_mission/LAYOUT.png b/assets/en/auto_mission/LAYOUT.png new file mode 100644 index 0000000..1211fb4 Binary files /dev/null and b/assets/en/auto_mission/LAYOUT.png differ diff --git a/assets/en/auto_mission/PRESET1_ON.png b/assets/en/auto_mission/PRESET1_ON.png new file mode 100644 index 0000000..a1f1865 Binary files /dev/null and b/assets/en/auto_mission/PRESET1_ON.png differ diff --git a/assets/en/auto_mission/PRESET2_ON.png b/assets/en/auto_mission/PRESET2_ON.png new file mode 100644 index 0000000..4ee0bff Binary files /dev/null and b/assets/en/auto_mission/PRESET2_ON.png differ diff --git a/assets/en/auto_mission/PRESET3_ON.png b/assets/en/auto_mission/PRESET3_ON.png new file mode 100644 index 0000000..f1a4e5f Binary files /dev/null and b/assets/en/auto_mission/PRESET3_ON.png differ diff --git a/assets/en/auto_mission/PRESET4_ON.png b/assets/en/auto_mission/PRESET4_ON.png new file mode 100644 index 0000000..7ea3b4b Binary files /dev/null and b/assets/en/auto_mission/PRESET4_ON.png differ diff --git a/assets/en/auto_mission/PRESET_FIRST_ITEM.png b/assets/en/auto_mission/PRESET_FIRST_ITEM.png new file mode 100644 index 0000000..a7a36e8 Binary files /dev/null and b/assets/en/auto_mission/PRESET_FIRST_ITEM.png differ diff --git a/assets/en/auto_mission/PRESET_LIST.png b/assets/en/auto_mission/PRESET_LIST.png new file mode 100644 index 0000000..7cc86d2 Binary files /dev/null and b/assets/en/auto_mission/PRESET_LIST.png differ diff --git a/assets/en/auto_mission/PRESET_WINDOW.png b/assets/en/auto_mission/PRESET_WINDOW.png new file mode 100644 index 0000000..3c6b126 Binary files /dev/null and b/assets/en/auto_mission/PRESET_WINDOW.png differ diff --git a/assets/zht/auto_mission/LAYOUT.png b/assets/zht/auto_mission/LAYOUT.png new file mode 100644 index 0000000..1211fb4 Binary files /dev/null and b/assets/zht/auto_mission/LAYOUT.png differ diff --git a/assets/zht/auto_mission/PRESET1_OFF.png b/assets/zht/auto_mission/PRESET1_OFF.png new file mode 100644 index 0000000..3d91231 Binary files /dev/null and b/assets/zht/auto_mission/PRESET1_OFF.png differ diff --git a/assets/zht/auto_mission/PRESET1_ON.png b/assets/zht/auto_mission/PRESET1_ON.png new file mode 100644 index 0000000..a1f1865 Binary files /dev/null and b/assets/zht/auto_mission/PRESET1_ON.png differ diff --git a/assets/zht/auto_mission/PRESET2_OFF.png b/assets/zht/auto_mission/PRESET2_OFF.png new file mode 100644 index 0000000..6c52159 Binary files /dev/null and b/assets/zht/auto_mission/PRESET2_OFF.png differ diff --git a/assets/zht/auto_mission/PRESET2_ON.png b/assets/zht/auto_mission/PRESET2_ON.png new file mode 100644 index 0000000..4ee0bff Binary files /dev/null and b/assets/zht/auto_mission/PRESET2_ON.png differ diff --git a/assets/zht/auto_mission/PRESET3_OFF.png b/assets/zht/auto_mission/PRESET3_OFF.png new file mode 100644 index 0000000..dc5ebe7 Binary files /dev/null and b/assets/zht/auto_mission/PRESET3_OFF.png differ diff --git a/assets/zht/auto_mission/PRESET3_ON.png b/assets/zht/auto_mission/PRESET3_ON.png new file mode 100644 index 0000000..f1a4e5f Binary files /dev/null and b/assets/zht/auto_mission/PRESET3_ON.png differ diff --git a/assets/zht/auto_mission/PRESET4_OFF.png b/assets/zht/auto_mission/PRESET4_OFF.png new file mode 100644 index 0000000..2dabfc9 Binary files /dev/null and b/assets/zht/auto_mission/PRESET4_OFF.png differ diff --git a/assets/zht/auto_mission/PRESET4_ON.png b/assets/zht/auto_mission/PRESET4_ON.png new file mode 100644 index 0000000..7ea3b4b Binary files /dev/null and b/assets/zht/auto_mission/PRESET4_ON.png differ diff --git a/assets/zht/auto_mission/PRESET_FIRST_ITEM.png b/assets/zht/auto_mission/PRESET_FIRST_ITEM.png new file mode 100644 index 0000000..a7a36e8 Binary files /dev/null and b/assets/zht/auto_mission/PRESET_FIRST_ITEM.png differ diff --git a/assets/zht/auto_mission/PRESET_LIST.png b/assets/zht/auto_mission/PRESET_LIST.png new file mode 100644 index 0000000..018fe6f Binary files /dev/null and b/assets/zht/auto_mission/PRESET_LIST.png differ diff --git a/assets/zht/auto_mission/PRESET_WINDOW.png b/assets/zht/auto_mission/PRESET_WINDOW.png new file mode 100644 index 0000000..3c6b126 Binary files /dev/null and b/assets/zht/auto_mission/PRESET_WINDOW.png differ diff --git a/config/template.json b/config/template.json index 697ff30..791d9d7 100644 --- a/config/template.json +++ b/config/template.json @@ -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" } }, diff --git a/module/config/argument/args.json b/module/config/argument/args.json index ab63e57..61e07ee 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -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", diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 86336c3..20c69ae 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -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] diff --git a/module/config/config_generated.py b/module/config/config_generated.py index de74565..c414261 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -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` diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index c9658ea..10b03c4 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -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", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index f37e56c..b4b76fb 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -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": { diff --git a/tasks/schedule/scroll_select.py b/module/ui/scroll_select.py similarity index 96% rename from tasks/schedule/scroll_select.py rename to module/ui/scroll_select.py index ef85395..c681817 100644 --- a/tasks/schedule/scroll_select.py +++ b/module/ui/scroll_select.py @@ -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) ) diff --git a/tasks/auto_mission/assets/assets_auto_mission.py b/tasks/auto_mission/assets/assets_auto_mission.py index 2cd5cce..cedfcca 100644 --- a/tasks/auto_mission/assets/assets_auto_mission.py +++ b/tasks/auto_mission/assets/assets_auto_mission.py @@ -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, diff --git a/tasks/auto_mission/auto_mission.py b/tasks/auto_mission/auto_mission.py index 0863a75..de57298 100644 --- a/tasks/auto_mission/auto_mission.py +++ b/tasks/auto_mission/auto_mission.py @@ -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) diff --git a/tasks/auto_mission/copilot.py b/tasks/auto_mission/copilot.py index f1cbfc7..a3fa177 100644 --- a/tasks/auto_mission/copilot.py +++ b/tasks/auto_mission/copilot.py @@ -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): diff --git a/tasks/auto_mission/ui.py b/tasks/auto_mission/ui.py index 2411786..a522960 100644 --- a/tasks/auto_mission/ui.py +++ b/tasks/auto_mission/ui.py @@ -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): diff --git a/tasks/schedule/ui.py b/tasks/schedule/ui.py index 354235d..29be0e5 100644 --- a/tasks/schedule/ui.py +++ b/tasks/schedule/ui.py @@ -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