BIN
assets/share/combat/skill/CHECK_A.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
assets/share/combat/skill/CHECK_E.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
assets/share/combat/skill/IN_SKILL.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
assets/share/combat/skill/USE_A.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/share/combat/skill/USE_E.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/share/combat/skill/USE_Q1.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
assets/share/combat/skill/USE_Q2.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
assets/share/combat/skill/USE_Q3.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
assets/share/combat/skill/USE_Q4.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
assets/share/combat/skill/USE_Q_AIM.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
assets/share/daily/trial/CHARACTER_TRIAL.BUTTON.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
assets/share/daily/trial/CHARACTER_TRIAL.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
assets/share/daily/trial/HIMEKO_CHECK.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
assets/share/daily/trial/HIMEKO_CLICK.SEARCH.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
assets/share/daily/trial/HIMEKO_CLICK.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
assets/share/daily/trial/INFO_CLOSE.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
assets/share/daily/trial/REGULAR_GACHA_CHECK.SEARCH.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
assets/share/daily/trial/REGULAR_GACHA_CHECK.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
assets/share/daily/trial/REGULAR_GACHA_CLICK.SEARCH.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
assets/share/daily/trial/REGULAR_GACHA_CLICK.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
assets/share/daily/trial/START_TRIAL.BUTTON.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/share/daily/trial/START_TRIAL.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
@ -75,19 +75,19 @@
|
||||
"Complete_Calyx_Crimson_1_time": "achievable",
|
||||
"Clear_Stagnant_Shadow_1_times": "achievable",
|
||||
"Clear_Cavern_of_Corrosion_1_times": "achievable",
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": "not_supported",
|
||||
"Inflict_Weakness_Break_5_times": "not_supported",
|
||||
"Defeat_a_total_of_20_enemies": "not_supported",
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": "not_supported",
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": "achievable",
|
||||
"Inflict_Weakness_Break_5_times": "achievable",
|
||||
"Defeat_a_total_of_20_enemies": "achievable",
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": "achievable",
|
||||
"Use_Technique_2_times": "achievable",
|
||||
"Go_on_assignment_1_time": "achievable",
|
||||
"Take_1_photo": "achievable",
|
||||
"Destroy_3_destructible_objects": "not_supported",
|
||||
"Destroy_3_destructible_objects": "achievable",
|
||||
"Complete_Forgotten_Hall_1_time": "achievable",
|
||||
"Complete_Echo_of_War_1_times": "not_supported",
|
||||
"Complete_1_stage_in_Simulated_Universe_Any_world": "not_supported",
|
||||
"Obtain_victory_in_combat_with_support_characters_1_time": "achievable",
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": "not_supported",
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": "achievable",
|
||||
"Level_up_any_character_1_time": "not_supported",
|
||||
"Level_up_any_Light_Cone_1_time": "not_supported",
|
||||
"Level_up_any_Relic_1_time": "not_supported",
|
||||
|
89
dev_tools/route_extract.py
Normal file
@ -0,0 +1,89 @@
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass, fields
|
||||
|
||||
from module.base.code_generator import CodeGenerator
|
||||
|
||||
|
||||
@dataclass
|
||||
class RouteData:
|
||||
name: str
|
||||
route: str
|
||||
plane: str
|
||||
floor: str = 'F1'
|
||||
position: tuple = None
|
||||
|
||||
|
||||
class RouteExtract:
|
||||
def __init__(self, folder):
|
||||
self.folder = folder
|
||||
|
||||
def iter_files(self):
|
||||
for path, folders, files in os.walk(self.folder):
|
||||
path = path.replace('\\', '/')
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
yield f'{path}/{file}'
|
||||
|
||||
def extract_route(self, file):
|
||||
print(f'Extract {file}')
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
"""
|
||||
def route_item_enemy(self):
|
||||
self.enter_himeko_trial()
|
||||
self.map_init(plane=Jarilo_BackwaterPass, position=(519.9, 361.5))
|
||||
"""
|
||||
regex = re.compile(
|
||||
r'def (?P<func>[a-zA-Z0-9_]*?)\(self\):.*?'
|
||||
r'self\.map_init\((.*?)\)'
|
||||
, re.DOTALL)
|
||||
file = file.replace(self.folder, '').replace('.py', '').replace('/', '_').strip('_')
|
||||
module = f"{self.folder.strip('./').replace('/', '.')}.{file.replace('_', '.')}"
|
||||
|
||||
for result in regex.findall(content):
|
||||
func, data = result
|
||||
|
||||
res = re.search(r'plane=([a-zA-Z_]*)', data)
|
||||
if res:
|
||||
plane = res.group(1)
|
||||
else:
|
||||
# Must contain plane
|
||||
continue
|
||||
res = re.search(r'floor=([\'"a-zA-Z0-9_]*)', data)
|
||||
if res:
|
||||
floor = res.group(1).strip('"\'')
|
||||
else:
|
||||
floor = 'F1'
|
||||
res = re.search(r'position=\(([0-9.]*)[, ]+([0-9.]*)', data)
|
||||
if res:
|
||||
position = (float(res.group(1)), float(res.group(2)))
|
||||
else:
|
||||
position = None
|
||||
|
||||
yield RouteData(
|
||||
name=f'{file}__{func}',
|
||||
route=f'{module}:{func}',
|
||||
plane=plane,
|
||||
floor=floor,
|
||||
position=position,
|
||||
)
|
||||
|
||||
def write(self, file):
|
||||
gen = CodeGenerator()
|
||||
gen.Import("""
|
||||
from tasks.map.route.base import RouteData
|
||||
""")
|
||||
for f in self.iter_files():
|
||||
for row in self.extract_route(f):
|
||||
with gen.Object(key=row.name, object_class='RouteData'):
|
||||
for key in fields(row):
|
||||
value = getattr(row, key.name)
|
||||
gen.ObjectAttr(key.name, value)
|
||||
gen.write(file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.chdir(os.path.join(os.path.dirname(__file__), '../'))
|
||||
RouteExtract('./route/daily').write('./tasks/map/route/route/daily.py')
|
@ -668,21 +668,21 @@ class ConfigUpdater:
|
||||
set_daily('Clear_Cavern_of_Corrosion_1_times',
|
||||
dungeon and deep_get(data, 'Dungeon.DungeonDaily.CavernOfCorrosion') != 'do_not_achieve')
|
||||
# Combat requirements
|
||||
set_daily('In_a_single_battle_inflict_3_Weakness_Break_of_different_Types', 'not_supported')
|
||||
set_daily('Inflict_Weakness_Break_5_times', 'not_supported')
|
||||
set_daily('Defeat_a_total_of_20_enemies', 'not_supported')
|
||||
set_daily('Enter_combat_by_attacking_enemy_Weakness_and_win_3_times', 'not_supported')
|
||||
set_daily('In_a_single_battle_inflict_3_Weakness_Break_of_different_Types', 'achievable')
|
||||
set_daily('Inflict_Weakness_Break_5_times', 'achievable')
|
||||
set_daily('Defeat_a_total_of_20_enemies', 'achievable')
|
||||
set_daily('Enter_combat_by_attacking_enemy_Weakness_and_win_3_times', 'achievable')
|
||||
set_daily('Use_Technique_2_times', 'achievable')
|
||||
# Other game systems
|
||||
set_daily('Go_on_assignment_1_time', deep_get(data, 'Assignment.Scheduler.Enable'))
|
||||
set_daily('Take_1_photo', 'achievable')
|
||||
set_daily('Destroy_3_destructible_objects', 'not_supported')
|
||||
set_daily('Destroy_3_destructible_objects', 'achievable')
|
||||
set_daily('Complete_Forgotten_Hall_1_time', 'achievable')
|
||||
set_daily('Complete_Echo_of_War_1_times', 'not_supported')
|
||||
set_daily('Complete_1_stage_in_Simulated_Universe_Any_world', 'not_supported')
|
||||
set_daily('Obtain_victory_in_combat_with_support_characters_1_time',
|
||||
dungeon and deep_get(data, 'Dungeon.DungeonSupport.Use') in ['when_daily', 'always_use'])
|
||||
set_daily('Use_an_Ultimate_to_deal_the_final_blow_1_time', 'not_supported')
|
||||
set_daily('Use_an_Ultimate_to_deal_the_final_blow_1_time', 'achievable')
|
||||
# Build
|
||||
set_daily('Level_up_any_character_1_time', 'not_supported')
|
||||
set_daily('Level_up_any_Light_Cone_1_time', 'not_supported')
|
||||
|
@ -437,28 +437,28 @@
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"name": "In a single battle, inflict 3 Weakness Break of different Types",
|
||||
"help": "",
|
||||
"help": "Achievable by default, will do Himeko trial",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"name": "Inflict Weakness Break 5 times",
|
||||
"help": "",
|
||||
"help": "Achievable by default, will do Himeko trial",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Defeat_a_total_of_20_enemies": {
|
||||
"name": "Defeat a total of 20 enemies",
|
||||
"help": "",
|
||||
"help": "Achievable by default, will do Himeko trial",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": {
|
||||
"name": "Enter combat by attacking enemy's Weakness and win 3 times",
|
||||
"help": "",
|
||||
"help": "Achievable by default, will do Himeko trial",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
@ -486,14 +486,14 @@
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"name": "Destroy 3 destructible objects",
|
||||
"help": "",
|
||||
"help": "Achievable by default, will do Himeko trial",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"name": "Complete Forgotten Hall 1 time",
|
||||
"help": "Choose the first four characters to do stage 1 once, please ensure that account build is sufficient to do stage 1",
|
||||
"help": "Achievable by default, will choose the first four characters to do stage 1 once, please ensure that account build is sufficient to do stage 1",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
@ -521,7 +521,7 @@
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"name": "Use an Ultimate to deal the final blow 1 time",
|
||||
"help": "",
|
||||
"help": "Achievable by default, will do Himeko trial",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
|
@ -437,28 +437,28 @@
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"name": "单场战斗中,触发3种不同属性的弱点击破",
|
||||
"help": "",
|
||||
"help": "默认可完成,将做姬子试用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"name": "累计触发弱点击破效果5次",
|
||||
"help": "",
|
||||
"help": "默认可完成,将做姬子试用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Defeat_a_total_of_20_enemies": {
|
||||
"name": "累计消灭20个敌人",
|
||||
"help": "",
|
||||
"help": "默认可完成,将做姬子试用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": {
|
||||
"name": "利用弱点进入战斗并获胜3次",
|
||||
"help": "",
|
||||
"help": "默认可完成,将做姬子试用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
@ -486,14 +486,14 @@
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"name": "累计击碎3个可破坏物",
|
||||
"help": "",
|
||||
"help": "默认可完成,将做姬子试用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"name": "完成1次「忘却之庭」",
|
||||
"help": "选前四个角色打一次深渊一,请保证帐号练度足够打深渊一",
|
||||
"help": "默认可完成,将使用前四个角色打一次深渊一,请保证帐号练度足够打深渊一",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
@ -521,7 +521,7 @@
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"name": "施放终结技造成制胜一击1次",
|
||||
"help": "",
|
||||
"help": "默认可完成,将做姬子试用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
|
@ -437,35 +437,35 @@
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"name": "單場戰鬥中,觸發3種不同屬性的弱點擊破",
|
||||
"help": "",
|
||||
"help": "預設可完成,將做姬子試用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"name": "累積觸發弱點擊破效果5次",
|
||||
"help": "",
|
||||
"help": "預設可完成,將做姬子試用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Defeat_a_total_of_20_enemies": {
|
||||
"name": "累積消滅20個敵人",
|
||||
"help": "",
|
||||
"help": "預設可完成,將做姬子試用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": {
|
||||
"name": "利用弱點進入戰鬥並獲勝3次",
|
||||
"help": "",
|
||||
"help": "預設可完成,將做姬子試用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Use_Technique_2_times": {
|
||||
"name": "累積施放2次秘技",
|
||||
"help": "默認可完成,將前往深淵一施放2次秘技",
|
||||
"help": "預設可完成,將前往深淵一施放2次秘技",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
@ -479,21 +479,21 @@
|
||||
},
|
||||
"Take_1_photo": {
|
||||
"name": "拍照1次",
|
||||
"help": "默认可完成",
|
||||
"help": "預設可完成",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"name": "累積擊碎3個可破壞物",
|
||||
"help": "",
|
||||
"help": "預設可完成,將做姬子試用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"name": "完成1次「忘卻之庭」",
|
||||
"help": "選前四個角色打一次深淵一,請確保帳號練度足夠打深淵一",
|
||||
"help": "預設可完成,將使用前四個角色打一次深淵一,請確保帳號練度足夠打深淵一",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
@ -521,7 +521,7 @@
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"name": "施放終結技造成制勝一擊1次",
|
||||
"help": "",
|
||||
"help": "預設可完成,將做姬子試用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
@ -549,28 +549,28 @@
|
||||
},
|
||||
"Salvage_any_Relic": {
|
||||
"name": "分解任意1件遺器",
|
||||
"help": "默認可完成,將分解遺器稀有度倒序的第一個",
|
||||
"help": "預設可完成,將分解遺器稀有度倒序的第一個",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Synthesize_Consumable_1_time": {
|
||||
"name": "合成1次消耗品",
|
||||
"help": "默認可完成,將合成最低級零食",
|
||||
"help": "預設可完成,將合成最低級零食",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Synthesize_material_1_time": {
|
||||
"name": "合成1次素材",
|
||||
"help": "默認可完成,將合成最低級素材",
|
||||
"help": "預設可完成,將合成最低級素材",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Use_Consumables_1_time": {
|
||||
"name": "使用1件消耗品",
|
||||
"help": "默認可完成,將使用護具,無材料時先合成再使用",
|
||||
"help": "預設可完成,將使用護具,無材料時先合成再使用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
|
122
route/daily/HimekoTrial.py
Normal file
@ -0,0 +1,122 @@
|
||||
from module.logger import logger
|
||||
from tasks.combat.combat import Combat
|
||||
from tasks.daily.assets.assets_daily_trial import START_TRIAL
|
||||
from tasks.daily.trail import CharacterTrial
|
||||
from tasks.map.control.waypoint import Waypoint
|
||||
from tasks.map.keywords.plane import Jarilo_BackwaterPass
|
||||
from tasks.map.route.base import RouteBase
|
||||
|
||||
|
||||
class Route(RouteBase, Combat, CharacterTrial):
|
||||
def handle_combat_state(self, auto=True, speed_2x=True):
|
||||
# No auto in character trial
|
||||
auto = False
|
||||
return super().handle_combat_state(auto=auto, speed_2x=speed_2x)
|
||||
|
||||
def wait_next_skill(self, expected_end=None, skip_first_screenshot=True):
|
||||
# Ended at START_TRIAL
|
||||
def combat_end():
|
||||
return self.match_template_color(START_TRIAL)
|
||||
|
||||
return super().wait_next_skill(expected_end=combat_end, skip_first_screenshot=skip_first_screenshot)
|
||||
|
||||
def combat_execute(self, expected_end=None):
|
||||
# Battle 1/3
|
||||
# Enemy cleared by follow up
|
||||
self.wait_next_skill()
|
||||
|
||||
# Battle 2/3
|
||||
# Himeko E
|
||||
# Rest are cleared by follow up
|
||||
self.use_E()
|
||||
self.wait_next_skill()
|
||||
|
||||
# Battle 3/3
|
||||
# Himeko E
|
||||
self.use_E()
|
||||
self.wait_next_skill()
|
||||
# Herta A, or Natasha A, depends on who wasn't being attacked
|
||||
self.use_A()
|
||||
self.wait_next_skill()
|
||||
# Natasha A, this will also cause weakness break
|
||||
# To achieve In_a_single_battle_inflict_3_Weakness_Break_of_different_Types
|
||||
self.use_A()
|
||||
self.wait_next_skill()
|
||||
# Himeko Q
|
||||
# To achieve Use_an_Ultimate_to_deal_the_final_blow_1_time
|
||||
# May kill the enemy
|
||||
self.use_Q(1)
|
||||
if not self.wait_next_skill():
|
||||
return
|
||||
# Herta Q
|
||||
# To achieve Use_an_Ultimate_to_deal_the_final_blow_1_time
|
||||
# May kill the enemy
|
||||
self.use_Q(2)
|
||||
if not self.wait_next_skill():
|
||||
return
|
||||
|
||||
# Combat should end here, just incase
|
||||
logger.warning(f'Himeko trial is not going as expected')
|
||||
for _ in range(3):
|
||||
self.use_E()
|
||||
if not self.wait_next_skill():
|
||||
return
|
||||
|
||||
def route_item_enemy(self):
|
||||
self.enter_himeko_trial()
|
||||
self.map_init(plane=Jarilo_BackwaterPass, position=(519.9, 361.5))
|
||||
|
||||
# Visit 3 items
|
||||
self.clear_item(
|
||||
Waypoint((587.6, 366.9)).run_2x(),
|
||||
)
|
||||
self.clear_item(
|
||||
Waypoint((575.5, 377.4)),
|
||||
)
|
||||
self.clear_item(
|
||||
# Go through arched door
|
||||
Waypoint((581.5, 383.3)).run().set_threshold(3),
|
||||
Waypoint((575.7, 417.2)).run(),
|
||||
)
|
||||
# Goto boss
|
||||
self.clear_enemy(
|
||||
Waypoint((613.5, 427.3)),
|
||||
)
|
||||
|
||||
def route_item(self):
|
||||
self.enter_himeko_trial()
|
||||
self.map_init(plane=Jarilo_BackwaterPass, position=(519.9, 361.5))
|
||||
|
||||
# Visit 3 items
|
||||
self.clear_item(
|
||||
Waypoint((587.6, 366.9)).run_2x(),
|
||||
)
|
||||
self.clear_item(
|
||||
Waypoint((575.5, 377.4)),
|
||||
)
|
||||
self.clear_item(
|
||||
# Go through arched door
|
||||
Waypoint((581.5, 383.3)).run().set_threshold(3),
|
||||
Waypoint((575.7, 417.2)).run(),
|
||||
)
|
||||
# Exit
|
||||
self.exit_trial()
|
||||
|
||||
def route_enemy(self):
|
||||
self.enter_himeko_trial()
|
||||
self.map_init(plane=Jarilo_BackwaterPass, position=(519.9, 361.5))
|
||||
|
||||
# Goto boss
|
||||
self.clear_enemy(
|
||||
# Before the corner, turn right
|
||||
Waypoint((571.7, 371.3)).run_2x(),
|
||||
# Go through arched door
|
||||
Waypoint((581.5, 383.3)).run_2x(),
|
||||
# Boss
|
||||
Waypoint((613.5, 427.3)).run_2x(),
|
||||
)
|
||||
|
||||
def exit(self):
|
||||
# Fake map_init to expose this method
|
||||
# self.map_init(plane=Jarilo_BackwaterPass, position=(519.9, 361.5))
|
||||
self.exit_trial_to_main()
|
105
tasks/combat/assets/assets_combat_skill.py
Normal file
@ -0,0 +1,105 @@
|
||||
from module.base.button import Button, ButtonWrapper
|
||||
|
||||
# This file was auto-generated, do not modify it manually. To generate:
|
||||
# ``` python -m dev_tools.button_extract ```
|
||||
|
||||
CHECK_A = ButtonWrapper(
|
||||
name='CHECK_A',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/CHECK_A.png',
|
||||
area=(967, 513, 1087, 623),
|
||||
search=(947, 493, 1107, 643),
|
||||
color=(91, 91, 97),
|
||||
button=(967, 513, 1087, 623),
|
||||
),
|
||||
)
|
||||
CHECK_E = ButtonWrapper(
|
||||
name='CHECK_E',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/CHECK_E.png',
|
||||
area=(1111, 429, 1261, 519),
|
||||
search=(1091, 409, 1280, 539),
|
||||
color=(80, 81, 89),
|
||||
button=(1111, 429, 1261, 519),
|
||||
),
|
||||
)
|
||||
IN_SKILL = ButtonWrapper(
|
||||
name='IN_SKILL',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/IN_SKILL.png',
|
||||
area=(35, 619, 59, 635),
|
||||
search=(15, 599, 79, 655),
|
||||
color=(106, 107, 110),
|
||||
button=(35, 619, 59, 635),
|
||||
),
|
||||
)
|
||||
USE_A = ButtonWrapper(
|
||||
name='USE_A',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/USE_A.png',
|
||||
area=(1037, 579, 1097, 639),
|
||||
search=(1017, 559, 1117, 659),
|
||||
color=(107, 108, 111),
|
||||
button=(1037, 579, 1097, 639),
|
||||
),
|
||||
)
|
||||
USE_E = ButtonWrapper(
|
||||
name='USE_E',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/USE_E.png',
|
||||
area=(1154, 490, 1214, 550),
|
||||
search=(1134, 470, 1234, 570),
|
||||
color=(120, 123, 127),
|
||||
button=(1154, 490, 1214, 550),
|
||||
),
|
||||
)
|
||||
USE_Q1 = ButtonWrapper(
|
||||
name='USE_Q1',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/USE_Q1.png',
|
||||
area=(217, 548, 253, 578),
|
||||
search=(197, 528, 273, 598),
|
||||
color=(145, 151, 225),
|
||||
button=(217, 548, 253, 578),
|
||||
),
|
||||
)
|
||||
USE_Q2 = ButtonWrapper(
|
||||
name='USE_Q2',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/USE_Q2.png',
|
||||
area=(405, 546, 441, 576),
|
||||
search=(385, 526, 461, 596),
|
||||
color=(155, 131, 207),
|
||||
button=(405, 546, 441, 576),
|
||||
),
|
||||
)
|
||||
USE_Q3 = ButtonWrapper(
|
||||
name='USE_Q3',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/USE_Q3.png',
|
||||
area=(593, 545, 629, 575),
|
||||
search=(573, 525, 649, 595),
|
||||
color=(208, 208, 202),
|
||||
button=(593, 545, 629, 575),
|
||||
),
|
||||
)
|
||||
USE_Q4 = ButtonWrapper(
|
||||
name='USE_Q4',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/USE_Q4.png',
|
||||
area=(781, 545, 817, 575),
|
||||
search=(761, 525, 837, 595),
|
||||
color=(151, 113, 206),
|
||||
button=(781, 545, 817, 575),
|
||||
),
|
||||
)
|
||||
USE_Q_AIM = ButtonWrapper(
|
||||
name='USE_Q_AIM',
|
||||
share=Button(
|
||||
file='./assets/share/combat/skill/USE_Q_AIM.png',
|
||||
area=(1074, 521, 1174, 621),
|
||||
search=(1054, 501, 1194, 641),
|
||||
color=(82, 60, 64),
|
||||
button=(1074, 521, 1174, 621),
|
||||
),
|
||||
)
|
@ -6,13 +6,14 @@ from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
|
||||
from tasks.combat.assets.assets_combat_team import COMBAT_TEAM_PREPARE, COMBAT_TEAM_SUPPORT
|
||||
from tasks.combat.interact import CombatInteract
|
||||
from tasks.combat.prepare import CombatPrepare
|
||||
from tasks.combat.skill import CombatSkill
|
||||
from tasks.combat.state import CombatState
|
||||
from tasks.combat.support import CombatSupport
|
||||
from tasks.combat.team import CombatTeam
|
||||
from tasks.map.control.joystick import MapControlJoystick
|
||||
|
||||
|
||||
class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSupport, MapControlJoystick):
|
||||
class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSupport, CombatSkill, MapControlJoystick):
|
||||
def handle_combat_prepare(self):
|
||||
"""
|
||||
Returns:
|
||||
|
126
tasks/combat/skill.py
Normal file
@ -0,0 +1,126 @@
|
||||
from module.base.button import match_template
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from tasks.base.ui import UI
|
||||
from tasks.combat.assets.assets_combat_skill import *
|
||||
|
||||
|
||||
class CombatSkill(UI):
|
||||
def is_in_skill(self) -> bool:
|
||||
"""
|
||||
Combat paused, require manual skill use
|
||||
"""
|
||||
if not self.appear(IN_SKILL):
|
||||
return False
|
||||
|
||||
if not self.image_color_count(IN_SKILL, color=(255, 255, 255), threshold=221, count=50):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _skill_click(self, button, skip_first_screenshot=True):
|
||||
"""
|
||||
Click a skill button.
|
||||
Not in skill page means skill has been used and skill animation is ongoing
|
||||
"""
|
||||
logger.info(f'Skill use: {button}')
|
||||
interval = Timer(1)
|
||||
clicked = False
|
||||
prev_image = None
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.is_in_skill():
|
||||
if interval.reached():
|
||||
prev_image = self.image_crop(button)
|
||||
self.device.click(button)
|
||||
interval.reset()
|
||||
clicked = True
|
||||
continue
|
||||
else:
|
||||
# Skill animation on going
|
||||
if clicked:
|
||||
logger.info(f'Skill used: {button}')
|
||||
break
|
||||
# New skill icon
|
||||
if prev_image is not None:
|
||||
if not match_template(self.image_crop(button), prev_image):
|
||||
logger.info(f'Skill used: {button}')
|
||||
break
|
||||
|
||||
def _skill_switch(self, check_button, click_button, skip_first_screenshot=True):
|
||||
"""
|
||||
Switch to A or E
|
||||
"""
|
||||
logger.info(f'Skill switch: {check_button}')
|
||||
interval = Timer(1)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# Raw brown border
|
||||
if self.image_color_count(check_button, color=(220, 196, 145), threshold=221, count=50):
|
||||
logger.info(f'Skill switched: {check_button}')
|
||||
break
|
||||
|
||||
if self.is_in_skill():
|
||||
if interval.reached():
|
||||
self.device.click(click_button)
|
||||
interval.reset()
|
||||
continue
|
||||
|
||||
def wait_next_skill(self, expected_end=None, skip_first_screenshot=True):
|
||||
"""
|
||||
Args:
|
||||
expected_end: A function returns bool, True represents end.
|
||||
skip_first_screenshot:
|
||||
|
||||
Returns:
|
||||
bool: True if is_in_skill
|
||||
False if triggered expected_end
|
||||
"""
|
||||
logger.info('Wait next skill')
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.is_in_skill():
|
||||
return True
|
||||
if callable(expected_end) and expected_end():
|
||||
logger.info(f'Combat execute ended at {expected_end.__name__}')
|
||||
return False
|
||||
|
||||
def use_A(self) -> bool:
|
||||
logger.hr('Use A')
|
||||
self._skill_switch(check_button=CHECK_A, click_button=USE_A)
|
||||
self._skill_click(USE_A)
|
||||
return True
|
||||
|
||||
def use_E(self) -> bool:
|
||||
logger.hr('Use E')
|
||||
self._skill_switch(check_button=CHECK_E, click_button=USE_E)
|
||||
self._skill_click(USE_E)
|
||||
return True
|
||||
|
||||
def use_Q(self, position: int) -> bool:
|
||||
"""
|
||||
Args:
|
||||
position: 1 to 4
|
||||
"""
|
||||
logger.hr(f'Use Q {position}')
|
||||
try:
|
||||
button = [USE_Q1, USE_Q2, USE_Q3, USE_Q4][position - 1]
|
||||
except IndexError:
|
||||
logger.error(f'use_Q: position {position} does not exist')
|
||||
return False
|
||||
|
||||
self._skill_click(button)
|
||||
self._skill_click(USE_Q_AIM)
|
||||
return True
|
75
tasks/daily/assets/assets_daily_trial.py
Normal file
@ -0,0 +1,75 @@
|
||||
from module.base.button import Button, ButtonWrapper
|
||||
|
||||
# This file was auto-generated, do not modify it manually. To generate:
|
||||
# ``` python -m dev_tools.button_extract ```
|
||||
|
||||
CHARACTER_TRIAL = ButtonWrapper(
|
||||
name='CHARACTER_TRIAL',
|
||||
share=Button(
|
||||
file='./assets/share/daily/trial/CHARACTER_TRIAL.png',
|
||||
area=(416, 672, 559, 677),
|
||||
search=(396, 652, 579, 697),
|
||||
color=(238, 238, 239),
|
||||
button=(414, 649, 561, 678),
|
||||
),
|
||||
)
|
||||
HIMEKO_CHECK = ButtonWrapper(
|
||||
name='HIMEKO_CHECK',
|
||||
share=Button(
|
||||
file='./assets/share/daily/trial/HIMEKO_CHECK.png',
|
||||
area=(597, 165, 646, 192),
|
||||
search=(577, 145, 666, 212),
|
||||
color=(207, 137, 123),
|
||||
button=(597, 165, 646, 192),
|
||||
),
|
||||
)
|
||||
HIMEKO_CLICK = ButtonWrapper(
|
||||
name='HIMEKO_CLICK',
|
||||
share=Button(
|
||||
file='./assets/share/daily/trial/HIMEKO_CLICK.png',
|
||||
area=(88, 142, 125, 178),
|
||||
search=(42, 124, 176, 425),
|
||||
color=(202, 144, 136),
|
||||
button=(88, 142, 125, 178),
|
||||
),
|
||||
)
|
||||
INFO_CLOSE = ButtonWrapper(
|
||||
name='INFO_CLOSE',
|
||||
share=Button(
|
||||
file='./assets/share/daily/trial/INFO_CLOSE.png',
|
||||
area=(1129, 222, 1159, 252),
|
||||
search=(1109, 202, 1179, 272),
|
||||
color=(69, 69, 70),
|
||||
button=(1129, 222, 1159, 252),
|
||||
),
|
||||
)
|
||||
REGULAR_GACHA_CHECK = ButtonWrapper(
|
||||
name='REGULAR_GACHA_CHECK',
|
||||
share=Button(
|
||||
file='./assets/share/daily/trial/REGULAR_GACHA_CHECK.png',
|
||||
area=(69, 332, 124, 367),
|
||||
search=(15, 146, 150, 478),
|
||||
color=(100, 92, 82),
|
||||
button=(69, 332, 124, 367),
|
||||
),
|
||||
)
|
||||
REGULAR_GACHA_CLICK = ButtonWrapper(
|
||||
name='REGULAR_GACHA_CLICK',
|
||||
share=Button(
|
||||
file='./assets/share/daily/trial/REGULAR_GACHA_CLICK.png',
|
||||
area=(63, 324, 118, 349),
|
||||
search=(15, 146, 150, 478),
|
||||
color=(36, 34, 30),
|
||||
button=(63, 324, 118, 349),
|
||||
),
|
||||
)
|
||||
START_TRIAL = ButtonWrapper(
|
||||
name='START_TRIAL',
|
||||
share=Button(
|
||||
file='./assets/share/daily/trial/START_TRIAL.png',
|
||||
area=(905, 634, 919, 674),
|
||||
search=(885, 614, 939, 694),
|
||||
color=(240, 238, 238),
|
||||
button=(919, 636, 1191, 672),
|
||||
),
|
||||
)
|
@ -23,6 +23,7 @@ from tasks.dungeon.ui import DungeonUI
|
||||
from tasks.item.consumable_usage import ConsumableUsageUI
|
||||
from tasks.item.relics import RelicsUI
|
||||
from tasks.map.route.loader import RouteLoader
|
||||
from tasks.map.route.route import ROUTE_DAILY
|
||||
|
||||
|
||||
class DailyQuestOcr(Ocr):
|
||||
@ -260,9 +261,50 @@ class DailyQuestUI(DungeonUI, RouteLoader):
|
||||
if RelicsUI(self.config, self.device).salvage_relic():
|
||||
done += 1
|
||||
if KEYWORDS_DAILY_QUEST.Complete_Forgotten_Hall_1_time in quests:
|
||||
self.route_run('daily.forgotten_hall.stage_1')
|
||||
self.route_run(ROUTE_DAILY.ForgottenHallStage1__route)
|
||||
done += 1
|
||||
|
||||
"""
|
||||
enemy x1 In_a_single_battle_inflict_3_Weakness_Break_of_different_Types
|
||||
enemy x1 Inflict_Weakness_Break_5_times
|
||||
enemy x2 Defeat_a_total_of_20_enemies
|
||||
enemy x3 Enter_combat_by_attacking_enemy_Weakness_and_win_3_times
|
||||
item x1 Destroy_3_destructible_objects
|
||||
enemy x1 Use_an_Ultimate_to_deal_the_final_blow_1_time
|
||||
"""
|
||||
enemy = 0
|
||||
item = 0
|
||||
quests = [
|
||||
KEYWORDS_DAILY_QUEST.Enter_combat_by_attacking_enemy_Weakness_and_win_3_times,
|
||||
]
|
||||
if KEYWORDS_DAILY_QUEST.In_a_single_battle_inflict_3_Weakness_Break_of_different_Types in quests:
|
||||
enemy = max(enemy, 1)
|
||||
if KEYWORDS_DAILY_QUEST.Inflict_Weakness_Break_5_times in quests:
|
||||
enemy = max(enemy, 1)
|
||||
if KEYWORDS_DAILY_QUEST.Defeat_a_total_of_20_enemies in quests:
|
||||
enemy = max(enemy, 2)
|
||||
if KEYWORDS_DAILY_QUEST.Enter_combat_by_attacking_enemy_Weakness_and_win_3_times in quests:
|
||||
enemy = max(enemy, 3)
|
||||
if KEYWORDS_DAILY_QUEST.Destroy_3_destructible_objects in quests:
|
||||
item = max(item, 1)
|
||||
if KEYWORDS_DAILY_QUEST.Use_an_Ultimate_to_deal_the_final_blow_1_time in quests:
|
||||
enemy = max(enemy, 1)
|
||||
logger.info(f'Himeko trial, enemy={enemy}, item={item}')
|
||||
for run in [1, 2, 3]:
|
||||
if enemy >= run and item >= run:
|
||||
self.route_run(ROUTE_DAILY.HimekoTrial__route_item_enemy)
|
||||
done += 1
|
||||
elif enemy >= run:
|
||||
self.route_run(ROUTE_DAILY.HimekoTrial__route_enemy)
|
||||
done += 1
|
||||
elif item >= run:
|
||||
self.route_run(ROUTE_DAILY.HimekoTrial__route_item)
|
||||
done += 1
|
||||
else:
|
||||
break
|
||||
if max(enemy, item) > 0:
|
||||
self.route_run(ROUTE_DAILY.HimekoTrial__exit)
|
||||
|
||||
return done
|
||||
|
||||
def run(self):
|
||||
|
119
tasks/daily/trail.py
Normal file
@ -0,0 +1,119 @@
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from tasks.base.assets.assets_base_page import CLOSE, MAP_EXIT
|
||||
from tasks.base.page import page_gacha
|
||||
from tasks.base.ui import UI
|
||||
from tasks.daily.assets.assets_daily_trial import *
|
||||
|
||||
|
||||
class CharacterTrial(UI):
|
||||
def enter_himeko_trial(self):
|
||||
"""
|
||||
Pages:
|
||||
in: Any
|
||||
out: page_main, in himeko trial
|
||||
"""
|
||||
logger.info('Enter Himeko trial')
|
||||
switched = False
|
||||
if self.match_template_color(HIMEKO_CHECK):
|
||||
logger.info(f'Already at {HIMEKO_CHECK}')
|
||||
elif self.match_template_color(REGULAR_GACHA_CHECK):
|
||||
logger.info(f'Already at {REGULAR_GACHA_CHECK}')
|
||||
elif self.ui_page_appear(page_gacha):
|
||||
logger.info(f'Already at {page_gacha}')
|
||||
else:
|
||||
switched = self.ui_ensure(page_gacha)
|
||||
|
||||
# page_gacha -> in himeko trial
|
||||
skip_first_screenshot = True
|
||||
info_closed = False
|
||||
info_timeout = Timer(2, count=4).start()
|
||||
first_gacha = switched
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# End
|
||||
if self.is_in_main():
|
||||
if info_closed:
|
||||
logger.info('In Himeko trial')
|
||||
break
|
||||
if info_timeout.reached():
|
||||
logger.info('In Himeko trial (wait INFO_CLOSE timeout)')
|
||||
break
|
||||
else:
|
||||
info_timeout.reset()
|
||||
|
||||
# In map
|
||||
if self.appear_then_click(INFO_CLOSE):
|
||||
info_closed = True
|
||||
continue
|
||||
# Switch to Himeko trial
|
||||
if self.appear(START_TRIAL) \
|
||||
and not self.appear(HIMEKO_CHECK) \
|
||||
and self.appear_then_click(HIMEKO_CLICK, interval=2):
|
||||
continue
|
||||
if self.appear(HIMEKO_CHECK) \
|
||||
and self.match_template_color(START_TRIAL, interval=2):
|
||||
self.device.click(START_TRIAL)
|
||||
continue
|
||||
# Switch to regular trial
|
||||
if self.match_template_color(REGULAR_GACHA_CHECK) \
|
||||
and self.match_color(CHARACTER_TRIAL, interval=2):
|
||||
self.device.click(CHARACTER_TRIAL)
|
||||
continue
|
||||
if self.match_template_color(REGULAR_GACHA_CLICK, interval=2):
|
||||
# Poor sleep indeed, clicks won't be response unless other elements are loaded
|
||||
# Waiting for gacha banner moving
|
||||
if first_gacha:
|
||||
self.device.sleep(0.3)
|
||||
first_gacha = False
|
||||
self.device.click(REGULAR_GACHA_CLICK)
|
||||
continue
|
||||
|
||||
def exit_trial(self, skip_first_screenshot=True):
|
||||
"""
|
||||
Pages:
|
||||
in: page_main, in trial
|
||||
out: START_TRIAL
|
||||
"""
|
||||
logger.info('Exit trial')
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.match_template_color(START_TRIAL):
|
||||
break
|
||||
if self.appear_then_click(MAP_EXIT):
|
||||
continue
|
||||
if self.handle_popup_confirm():
|
||||
continue
|
||||
|
||||
def exit_trial_to_main(self, skip_first_screenshot=True):
|
||||
"""
|
||||
Pages:
|
||||
in: START_TRIAL
|
||||
out: page_main
|
||||
"""
|
||||
logger.info('Exit trial to main')
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
if self.is_in_main():
|
||||
break
|
||||
|
||||
if self.match_template_color(START_TRIAL, interval=2):
|
||||
logger.info(f'{START_TRIAL} -> {CLOSE}')
|
||||
self.device.click(CLOSE)
|
||||
continue
|
||||
if self.match_color(CHARACTER_TRIAL, interval=2):
|
||||
logger.info(f'{CHARACTER_TRIAL} -> {CLOSE}')
|
||||
self.device.click(CLOSE)
|
||||
continue
|
@ -2,6 +2,7 @@ from functools import cached_property
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from tasks.base.assets.assets_base_page import CLOSE
|
||||
from tasks.combat.combat import Combat
|
||||
from tasks.map.assets.assets_map_control import ROTATION_SWIPE_AREA
|
||||
from tasks.map.control.joystick import JoystickContact
|
||||
@ -74,6 +75,18 @@ class MapControl(Combat, AimDetectorMixin):
|
||||
interval.reset()
|
||||
continue
|
||||
|
||||
def walk_additional(self) -> bool:
|
||||
"""
|
||||
Handle popups during walk
|
||||
|
||||
Returns:
|
||||
bool: If handled
|
||||
"""
|
||||
if self.appear_then_click(CLOSE):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _goto(
|
||||
self,
|
||||
contact: JoystickContact,
|
||||
@ -139,6 +152,10 @@ class MapControl(Combat, AimDetectorMixin):
|
||||
self.combat_execute()
|
||||
if waypoint.early_stop:
|
||||
return result
|
||||
if self.walk_additional():
|
||||
attacked_enemy.clear()
|
||||
attacked_item.clear()
|
||||
continue
|
||||
|
||||
# The following detection require page_main
|
||||
if not self.is_in_main():
|
||||
|
@ -1,8 +1,19 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from tasks.map.control.control import MapControl
|
||||
from tasks.map.control.waypoint import Waypoint
|
||||
from tasks.map.keywords import MapPlane
|
||||
|
||||
|
||||
@dataclass
|
||||
class RouteData:
|
||||
name: str
|
||||
route: str
|
||||
plane: str
|
||||
floor: str = 'F1'
|
||||
position: tuple = None
|
||||
|
||||
|
||||
class RouteBase(MapControl):
|
||||
"""
|
||||
Base class of `Route`
|
||||
|
@ -1,38 +1,71 @@
|
||||
import importlib
|
||||
import os
|
||||
|
||||
from module.exception import RequestHumanTakeover
|
||||
from module.base.decorator import del_cached_property
|
||||
from module.exception import ScriptError
|
||||
from module.logger import logger
|
||||
from tasks.base.ui import UI
|
||||
from tasks.map.route.base import RouteBase
|
||||
from tasks.map.route.base import RouteBase, RouteData
|
||||
|
||||
|
||||
class RouteLoader(UI):
|
||||
route: RouteBase
|
||||
route_module: str = ''
|
||||
route_func: str = ''
|
||||
route_obj: RouteBase
|
||||
|
||||
def route_run(self, route: str):
|
||||
def route_delete(self):
|
||||
del_cached_property(self, 'route_obj')
|
||||
self.route_module = ''
|
||||
self.route_func = ''
|
||||
|
||||
def route_run(self, route: RouteData | str):
|
||||
"""
|
||||
Args:
|
||||
route: .py module path such as `daily.forgotten_hall.stage1`
|
||||
which will load `./route/daily/forgotten_hall/stage1.py`
|
||||
route: .py module path such as `route.daily.ForgottenHallStage1:route`
|
||||
which will load `./route/daily/ForgottenHallStage1.py` and run `Route.route()`
|
||||
"""
|
||||
folder, name = route.rsplit('.', maxsplit=1)
|
||||
path = f'./route/{route.replace(".", "/")}.py'
|
||||
logger.hr('Route run', level=1)
|
||||
if isinstance(route, RouteData):
|
||||
route = route.route
|
||||
logger.attr('Route', route)
|
||||
try:
|
||||
module = importlib.import_module(f'route.{folder}.{name}')
|
||||
module, func = route.split(':')
|
||||
except ValueError:
|
||||
logger.critical(f'Route invalid: {route}')
|
||||
raise ScriptError
|
||||
path = f'./{module.replace(".", "/")}.py'
|
||||
|
||||
# Import route file
|
||||
try:
|
||||
module_obj = importlib.import_module(f'{module}')
|
||||
except ModuleNotFoundError:
|
||||
logger.critical(f'Route file not found: {route} ({path})')
|
||||
logger.critical(f'Route file not found: {module} ({path})')
|
||||
if not os.path.exists(path):
|
||||
logger.critical(f'Route file not exists: {path}')
|
||||
raise RequestHumanTakeover
|
||||
raise ScriptError
|
||||
|
||||
# config = copy.deepcopy(self.config).merge(module.Config())
|
||||
config = self.config
|
||||
device = self.device
|
||||
# Create route object
|
||||
# Reuse the previous one
|
||||
if self.route_module != module:
|
||||
# config = copy.deepcopy(self.config).merge(module.Config())
|
||||
config = self.config
|
||||
device = self.device
|
||||
try:
|
||||
self.route_obj = module_obj.Route(config=config, device=device)
|
||||
except AttributeError as e:
|
||||
logger.critical(e)
|
||||
logger.critical(f'Route file {route} ({path}) must define class Route')
|
||||
raise ScriptError
|
||||
self.route_module = module
|
||||
|
||||
# Get route func
|
||||
try:
|
||||
self.route = module.Route(config=config, device=device)
|
||||
return self.route.route()
|
||||
func_obj = self.route_obj.__getattribute__(func)
|
||||
except AttributeError as e:
|
||||
logger.critical(e)
|
||||
logger.critical(f'Route file {route} ({path}) must define Route.route()')
|
||||
raise RequestHumanTakeover
|
||||
logger.critical(f'Route class in {route} ({path}) does not have method {func}')
|
||||
raise ScriptError
|
||||
self.route_func = func
|
||||
|
||||
# Run
|
||||
func_obj()
|
||||
|
1
tasks/map/route/route/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
import tasks.map.route.route.daily as ROUTE_DAILY
|
38
tasks/map/route/route/daily.py
Normal file
@ -0,0 +1,38 @@
|
||||
from tasks.map.route.base import RouteData
|
||||
|
||||
|
||||
ForgottenHallStage1__route = RouteData(
|
||||
name='ForgottenHallStage1__route',
|
||||
route='route.daily.ForgottenHallStage1:route',
|
||||
plane='Jarilo_BackwaterPass',
|
||||
floor='F1',
|
||||
position=(369.4, 643.4),
|
||||
)
|
||||
HimekoTrial__route_item_enemy = RouteData(
|
||||
name='HimekoTrial__route_item_enemy',
|
||||
route='route.daily.HimekoTrial:route_item_enemy',
|
||||
plane='Jarilo_BackwaterPass',
|
||||
floor='F1',
|
||||
position=(519.9, 361.5),
|
||||
)
|
||||
HimekoTrial__route_item = RouteData(
|
||||
name='HimekoTrial__route_item',
|
||||
route='route.daily.HimekoTrial:route_item',
|
||||
plane='Jarilo_BackwaterPass',
|
||||
floor='F1',
|
||||
position=(519.9, 361.5),
|
||||
)
|
||||
HimekoTrial__route_enemy = RouteData(
|
||||
name='HimekoTrial__route_enemy',
|
||||
route='route.daily.HimekoTrial:route_enemy',
|
||||
plane='Jarilo_BackwaterPass',
|
||||
floor='F1',
|
||||
position=(519.9, 361.5),
|
||||
)
|
||||
HimekoTrial__exit = RouteData(
|
||||
name='HimekoTrial__exit',
|
||||
route='route.daily.HimekoTrial:exit',
|
||||
plane='Jarilo_BackwaterPass',
|
||||
floor='F1',
|
||||
position=(519.9, 361.5),
|
||||
)
|