mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-22 00:35:34 +00:00
Merge remote-tracking branch 'upstream/dev' into add/rouge_buff_selector
# Conflicts: # module/config/argument/argument.yaml
This commit is contained in:
commit
9a17af2d12
@ -72,8 +72,8 @@ python gui.py
|
||||
|
||||
Discord https://discord.gg/aJkt3mKDEr QQ群 752620927
|
||||
|
||||
- [小地图识别原理](https://github.com/LmeSzinc/StarRailCopilot/wiki/MinimapTracking)
|
||||
- 开发文档(目录在侧边栏):[Alas wiki](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/1.-Start),但很多内容是新写的,建议阅读源码和历史提交。
|
||||
|
||||
- 开发路线图:[#10](https://github.com/LmeSzinc/StarRailCopilot/issues/10) ,欢迎提交 PR,挑选你感兴趣的部分进行开发即可。
|
||||
|
||||
> **如何添加多语言/多服务器支持?** 需要适配 assets,参考 [开发文档 “添加一个 Button” 一节](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/4.1.-Detection-objects#%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA-button)。
|
||||
|
@ -71,6 +71,8 @@ Keep the bot running, SRC will auto login and empty trailblaze power when it's r
|
||||
|
||||
Discord https://discord.gg/aJkt3mKDEr QQ Group 752620927
|
||||
|
||||
- [Minimap Tracking](https://github.com/LmeSzinc/StarRailCopilot/wiki/MinimapTracking)
|
||||
|
||||
- Development Docs (menu is on sidebar): [Alas wiki](https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/1.-Start) (in Chinese). However, there is ton of code newly written, it is recommended to read the source code and historical commits.
|
||||
|
||||
- Development Road Map: [#10](https://github.com/LmeSzinc/StarRailCopilot/issues/10). Pull Requests are welcomed, just pick the part you interested to work on.
|
||||
|
BIN
assets/character/Kafka.png
Normal file
BIN
assets/character/Kafka.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
assets/character/Luka.png
Normal file
BIN
assets/character/Luka.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@ -59,6 +59,26 @@ select {
|
||||
background-image: url("data:image/png;base64,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");
|
||||
}
|
||||
|
||||
.state > select {
|
||||
border-bottom: 0;
|
||||
background-image: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.state-bold > select {
|
||||
font-weight: bold;
|
||||
color: #7a77bb;
|
||||
}
|
||||
|
||||
.state-light > select {
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
[id^="pywebio-scope-arg_stored-stored-value-"] > div > input {
|
||||
border-bottom: none;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: 1px solid #21262d;
|
||||
}
|
||||
|
@ -60,6 +60,26 @@ select {
|
||||
background-image: url("data:image/png;base64,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");
|
||||
}
|
||||
|
||||
.state > select {
|
||||
border-bottom: 0;
|
||||
background-image: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.state-bold > select {
|
||||
font-weight: bold;
|
||||
color: #7a77bb;
|
||||
}
|
||||
|
||||
.state-light > select {
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
[id^="pywebio-scope-arg_stored-stored-value-"] > div > input {
|
||||
border-bottom: none;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: 1px solid lightgrey;
|
||||
}
|
||||
|
BIN
assets/share/dungeon/event/DOUBLE_RELIC_EVENT_TAG.png
Normal file
BIN
assets/share/dungeon/event/DOUBLE_RELIC_EVENT_TAG.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
assets/share/map/control/ROTATION_SWIPE_AREA.png
Normal file
BIN
assets/share/map/control/ROTATION_SWIPE_AREA.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
@ -42,9 +42,21 @@
|
||||
"Dungeon": {
|
||||
"Name": "Calyx_Golden_Treasures",
|
||||
"NameAtDoubleCalyx": "Calyx_Golden_Treasures",
|
||||
"Team": 1,
|
||||
"Support": "when_daily",
|
||||
"SupportCharacter": "FirstCharacter"
|
||||
"NameAtDoubleRelic": "Cavern_of_Corrosion_Path_of_Providence",
|
||||
"Team": 1
|
||||
},
|
||||
"DungeonDaily": {
|
||||
"CalyxGolden": "Calyx_Golden_Treasures",
|
||||
"CalyxCrimson": "Calyx_Crimson_Erudition",
|
||||
"StagnantShadow": "do_not_archive",
|
||||
"CavernOfCorrosion": "Cavern_of_Corrosion_Path_of_Providence"
|
||||
},
|
||||
"DungeonSupport": {
|
||||
"Use": "when_daily",
|
||||
"Character": "FirstCharacter"
|
||||
},
|
||||
"DungeonStorage": {
|
||||
"DungeonDouble": {}
|
||||
}
|
||||
},
|
||||
"DailyQuest": {
|
||||
@ -53,6 +65,37 @@
|
||||
"NextRun": "2020-01-01 00:00:00",
|
||||
"Command": "DailyQuest",
|
||||
"ServerUpdate": "04:00"
|
||||
},
|
||||
"AchievableQuest": {
|
||||
"Complete_1_Daily_Mission": "not_supported",
|
||||
"Clear_Calyx_Golden_1_times": "not_set",
|
||||
"Complete_Calyx_Crimson_1_time": "not_set",
|
||||
"Clear_Stagnant_Shadow_1_times": "not_set",
|
||||
"Clear_Cavern_of_Corrosion_1_times": "not_set",
|
||||
"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",
|
||||
"Use_Technique_2_times": "achievable",
|
||||
"Go_on_assignment_1_time": "not_set",
|
||||
"Take_1_photo": "achievable",
|
||||
"Destroy_3_destructible_objects": "not_supported",
|
||||
"Complete_Forgotten_Hall_1_time": "not_supported",
|
||||
"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": "not_set",
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": "not_supported",
|
||||
"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",
|
||||
"Salvage_any_Relic": "achievable",
|
||||
"Synthesize_Consumable_1_time": "achievable",
|
||||
"Synthesize_material_1_time": "achievable",
|
||||
"Use_Consumables_1_time": "achievable"
|
||||
},
|
||||
"DailyStorage": {
|
||||
"DailyActivity": {},
|
||||
"DailyQuest": {}
|
||||
}
|
||||
},
|
||||
"BattlePass": {
|
||||
|
@ -112,6 +112,10 @@ class Timer:
|
||||
else:
|
||||
return 0.
|
||||
|
||||
def set_current(self, current, count=0):
|
||||
self._current = time.time() - current
|
||||
self._reach_count = count
|
||||
|
||||
def reached(self):
|
||||
"""
|
||||
Returns:
|
||||
|
@ -218,6 +218,20 @@
|
||||
"Calyx_Crimson_Nihility"
|
||||
]
|
||||
},
|
||||
"NameAtDoubleRelic": {
|
||||
"type": "select",
|
||||
"value": "Cavern_of_Corrosion_Path_of_Providence",
|
||||
"option": [
|
||||
"do_not_participate",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting",
|
||||
"Cavern_of_Corrosion_Path_of_Providence",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers"
|
||||
]
|
||||
},
|
||||
"Team": {
|
||||
"type": "select",
|
||||
"value": 1,
|
||||
@ -229,17 +243,76 @@
|
||||
5,
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
"DungeonDaily": {
|
||||
"CalyxGolden": {
|
||||
"type": "select",
|
||||
"value": "Calyx_Golden_Treasures",
|
||||
"option": [
|
||||
"do_not_achieve",
|
||||
"Calyx_Golden_Memories",
|
||||
"Calyx_Golden_Aether",
|
||||
"Calyx_Golden_Treasures"
|
||||
]
|
||||
},
|
||||
"Support": {
|
||||
"CalyxCrimson": {
|
||||
"type": "select",
|
||||
"value": "Calyx_Crimson_Erudition",
|
||||
"option": [
|
||||
"do_not_achieve",
|
||||
"Calyx_Crimson_Destruction",
|
||||
"Calyx_Crimson_Preservation",
|
||||
"Calyx_Crimson_Hunt",
|
||||
"Calyx_Crimson_Abundance",
|
||||
"Calyx_Crimson_Erudition",
|
||||
"Calyx_Crimson_Harmony",
|
||||
"Calyx_Crimson_Nihility"
|
||||
]
|
||||
},
|
||||
"StagnantShadow": {
|
||||
"type": "select",
|
||||
"value": "do_not_archive",
|
||||
"option": [
|
||||
"do_not_achieve",
|
||||
"Stagnant_Shadow_Quanta",
|
||||
"Stagnant_Shadow_Gust",
|
||||
"Stagnant_Shadow_Fulmination",
|
||||
"Stagnant_Shadow_Blaze",
|
||||
"Stagnant_Shadow_Spike",
|
||||
"Stagnant_Shadow_Rime",
|
||||
"Stagnant_Shadow_Mirage",
|
||||
"Stagnant_Shadow_Icicle",
|
||||
"Stagnant_Shadow_Doom",
|
||||
"Stagnant_Shadow_Celestial"
|
||||
]
|
||||
},
|
||||
"CavernOfCorrosion": {
|
||||
"type": "select",
|
||||
"value": "Cavern_of_Corrosion_Path_of_Providence",
|
||||
"option": [
|
||||
"do_not_achieve",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting",
|
||||
"Cavern_of_Corrosion_Path_of_Providence",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers"
|
||||
]
|
||||
}
|
||||
},
|
||||
"DungeonSupport": {
|
||||
"Use": {
|
||||
"type": "select",
|
||||
"value": "when_daily",
|
||||
"option": [
|
||||
"do_not_use",
|
||||
"always_use",
|
||||
"when_daily"
|
||||
"when_daily",
|
||||
"do_not_use"
|
||||
]
|
||||
},
|
||||
"SupportCharacter": {
|
||||
"Character": {
|
||||
"type": "select",
|
||||
"value": "FirstCharacter",
|
||||
"option": [
|
||||
@ -256,6 +329,8 @@
|
||||
"Himeko",
|
||||
"Hook",
|
||||
"JingYuan",
|
||||
"Kafka",
|
||||
"Luka",
|
||||
"Luocha",
|
||||
"March7th",
|
||||
"Natasha",
|
||||
@ -274,6 +349,14 @@
|
||||
"Yukong"
|
||||
]
|
||||
}
|
||||
},
|
||||
"DungeonStorage": {
|
||||
"DungeonDouble": {
|
||||
"type": "stored",
|
||||
"value": {},
|
||||
"display": "hide",
|
||||
"stored": "StoredDungeonDouble"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DailyQuest": {
|
||||
@ -297,6 +380,397 @@
|
||||
"value": "04:00",
|
||||
"display": "hide"
|
||||
}
|
||||
},
|
||||
"AchievableQuest": {
|
||||
"Complete_1_Daily_Mission": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Clear_Calyx_Golden_1_times": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Complete_Calyx_Crimson_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Clear_Stagnant_Shadow_1_times": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Clear_Cavern_of_Corrosion_1_times": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Defeat_a_total_of_20_enemies": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Use_Technique_2_times": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Go_on_assignment_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Take_1_photo": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Complete_Echo_of_War_1_times": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Complete_1_stage_in_Simulated_Universe_Any_world": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Obtain_victory_in_combat_with_support_characters_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Level_up_any_character_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Level_up_any_Light_Cone_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Level_up_any_Relic_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Salvage_any_Relic": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Synthesize_Consumable_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Synthesize_material_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
},
|
||||
"Use_Consumables_1_time": {
|
||||
"type": "state",
|
||||
"value": "achievable",
|
||||
"option": [
|
||||
"achievable",
|
||||
"not_set",
|
||||
"not_supported"
|
||||
],
|
||||
"option_bold": [
|
||||
"achievable"
|
||||
],
|
||||
"option_light": [
|
||||
"not_supported"
|
||||
]
|
||||
}
|
||||
},
|
||||
"DailyStorage": {
|
||||
"DailyActivity": {
|
||||
"type": "stored",
|
||||
"value": {},
|
||||
"display": "hide",
|
||||
"stored": "StoredDailyActivity"
|
||||
},
|
||||
"DailyQuest": {
|
||||
"type": "stored",
|
||||
"value": {},
|
||||
"display": "hide",
|
||||
"stored": "StoredDaily"
|
||||
}
|
||||
}
|
||||
},
|
||||
"BattlePass": {
|
||||
|
@ -71,41 +71,74 @@ Optimization:
|
||||
|
||||
Dungeon:
|
||||
Name:
|
||||
# Options will be injected in config updater
|
||||
# Dungeon names will be injected in config updater
|
||||
value: Calyx_Golden_Treasures
|
||||
option: [ Calyx_Golden_Treasures, ]
|
||||
option: [ ]
|
||||
NameAtDoubleCalyx:
|
||||
# Options will be injected in config updater
|
||||
value: Calyx_Golden_Treasures
|
||||
option: [ do_not_participate, ]
|
||||
NameAtDoubleRelic:
|
||||
value: Cavern_of_Corrosion_Path_of_Providence
|
||||
option: [ do_not_participate, ]
|
||||
Team:
|
||||
value: 1
|
||||
option: [ 1, 2, 3, 4, 5, 6 ]
|
||||
Support:
|
||||
DungeonDaily:
|
||||
# Dungeon names will be injected in config updater
|
||||
CalyxGolden:
|
||||
value: Calyx_Golden_Treasures
|
||||
option: [ do_not_achieve, ]
|
||||
CalyxCrimson:
|
||||
value: Calyx_Crimson_Erudition
|
||||
option: [ do_not_achieve, ]
|
||||
StagnantShadow:
|
||||
value: do_not_archive
|
||||
option: [ do_not_achieve, ]
|
||||
CavernOfCorrosion:
|
||||
value: Cavern_of_Corrosion_Path_of_Providence
|
||||
option: [ do_not_achieve, ]
|
||||
DungeonSupport:
|
||||
Use:
|
||||
value: when_daily
|
||||
option: [ do_not_use, always_use, when_daily ]
|
||||
SupportCharacter:
|
||||
option: [ always_use, when_daily, do_not_use ]
|
||||
Character:
|
||||
# Options will be injected in config updater
|
||||
value: FirstCharacter
|
||||
option: [ FirstCharacter, ]
|
||||
DungeonStorage:
|
||||
DungeonDouble:
|
||||
stored: StoredDungeonDouble
|
||||
|
||||
AchievableQuest:
|
||||
# Quests will be injected in config updater
|
||||
# Complete_1_Daily_Mission:
|
||||
# type: state
|
||||
# value: achievable
|
||||
# option: [ achievable, not_set, not_supported ]
|
||||
# option_bold: [ achievable, ]
|
||||
DailyStorage:
|
||||
DailyActivity:
|
||||
stored: StoredDailyActivity
|
||||
DailyQuest:
|
||||
stored: StoredDaily
|
||||
|
||||
Assignment:
|
||||
Duration:
|
||||
value: 20
|
||||
option: [ 4, 8, 12, 20 ]
|
||||
option: [4, 8, 12, 20]
|
||||
# Options in Name_x will be injected in config updater
|
||||
Name_1:
|
||||
value: Nameless_Land_Nameless_People
|
||||
option: [ Nameless_Land_Nameless_People, ]
|
||||
option: [Nameless_Land_Nameless_People, ]
|
||||
Name_2:
|
||||
value: Akashic_Records
|
||||
option: [ Nameless_Land_Nameless_People, ]
|
||||
option: [Nameless_Land_Nameless_People, ]
|
||||
Name_3:
|
||||
value: The_Invisible_Hand
|
||||
option: [ Nameless_Land_Nameless_People, ]
|
||||
option: [Nameless_Land_Nameless_People, ]
|
||||
Name_4:
|
||||
value: Nine_Billion_Names
|
||||
option: [ Nameless_Land_Nameless_People, ]
|
||||
option: [Nameless_Land_Nameless_People, ]
|
||||
|
||||
# ==================== Rogue ====================
|
||||
|
||||
|
@ -25,8 +25,13 @@ Daily:
|
||||
Dungeon:
|
||||
- Scheduler
|
||||
- Dungeon
|
||||
- DungeonDaily
|
||||
- DungeonSupport
|
||||
- DungeonStorage
|
||||
DailyQuest:
|
||||
- Scheduler
|
||||
- AchievableQuest
|
||||
- DailyStorage
|
||||
BattlePass:
|
||||
- Scheduler
|
||||
Assignment:
|
||||
|
@ -3,11 +3,14 @@ import datetime
|
||||
import operator
|
||||
import threading
|
||||
|
||||
from module.base.decorator import cached_property, del_cached_property
|
||||
from module.base.filter import Filter
|
||||
from module.base.utils import SelectedGrids
|
||||
from module.config.config_generated import GeneratedConfig
|
||||
from module.config.config_manual import ManualConfig
|
||||
from module.config.config_updater import ConfigUpdater
|
||||
from module.config.stored.stored_generated import StoredGenerated
|
||||
from module.config.stored.classes import iter_attribute
|
||||
from module.config.utils import *
|
||||
from module.config.watcher import ConfigWatcher
|
||||
from module.exception import RequestHumanTakeover, ScriptError
|
||||
@ -168,6 +171,15 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig, ConfigWatcher
|
||||
self.data, keys="Alas.Optimization.CloseGameDuringWait", default=False
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def stored(self) -> StoredGenerated:
|
||||
stored = StoredGenerated()
|
||||
# Bind config
|
||||
for _, value in iter_attribute(stored):
|
||||
value._bind(self)
|
||||
del_cached_property(value, '_stored')
|
||||
return stored
|
||||
|
||||
def get_next_task(self):
|
||||
"""
|
||||
Calculate tasks, set pending_task and waiting_task
|
||||
@ -241,6 +253,7 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig, ConfigWatcher
|
||||
)
|
||||
# Don't use self.modified = {}, that will create a new object.
|
||||
self.modified.clear()
|
||||
del_cached_property(self, 'stored')
|
||||
self.write_file(self.config_name, data=self.data)
|
||||
|
||||
def update(self):
|
||||
@ -471,6 +484,20 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig, ConfigWatcher
|
||||
def is_task_enabled(self, task):
|
||||
return bool(self.cross_get(keys=[task, 'Scheduler', 'Enable'], default=False))
|
||||
|
||||
def update_daily_quests(self):
|
||||
"""
|
||||
Raises:
|
||||
TaskEnd: Call task `DailyQuest` and stop current task
|
||||
"""
|
||||
if self.stored.DailyActivity.is_expired():
|
||||
logger.info('Daily activity expired, call task to update')
|
||||
self.task_call('DailyQuest')
|
||||
self.task_stop()
|
||||
if self.stored.DailyQuest.is_expired():
|
||||
logger.info('Daily quests expired, call task to update')
|
||||
self.task_call('DailyQuest')
|
||||
self.task_stop()
|
||||
|
||||
@property
|
||||
def DEVICE_SCREENSHOT_METHOD(self):
|
||||
return self.Emulator_ScreenshotMethod
|
||||
|
@ -41,9 +41,52 @@ class GeneratedConfig:
|
||||
# Group `Dungeon`
|
||||
Dungeon_Name = 'Calyx_Golden_Treasures' # Calyx_Golden_Memories, Calyx_Golden_Aether, Calyx_Golden_Treasures, Calyx_Crimson_Destruction, Calyx_Crimson_Preservation, Calyx_Crimson_Hunt, Calyx_Crimson_Abundance, Calyx_Crimson_Erudition, Calyx_Crimson_Harmony, Calyx_Crimson_Nihility, Stagnant_Shadow_Quanta, Stagnant_Shadow_Gust, Stagnant_Shadow_Fulmination, Stagnant_Shadow_Blaze, Stagnant_Shadow_Spike, Stagnant_Shadow_Rime, Stagnant_Shadow_Mirage, Stagnant_Shadow_Icicle, Stagnant_Shadow_Doom, Stagnant_Shadow_Celestial, Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers
|
||||
Dungeon_NameAtDoubleCalyx = 'Calyx_Golden_Treasures' # do_not_participate, Calyx_Golden_Memories, Calyx_Golden_Aether, Calyx_Golden_Treasures, Calyx_Crimson_Destruction, Calyx_Crimson_Preservation, Calyx_Crimson_Hunt, Calyx_Crimson_Abundance, Calyx_Crimson_Erudition, Calyx_Crimson_Harmony, Calyx_Crimson_Nihility
|
||||
Dungeon_NameAtDoubleRelic = 'Cavern_of_Corrosion_Path_of_Providence' # do_not_participate, Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers
|
||||
Dungeon_Team = 1 # 1, 2, 3, 4, 5, 6
|
||||
Dungeon_Support = 'when_daily' # do_not_use, always_use, when_daily
|
||||
Dungeon_SupportCharacter = 'FirstCharacter' # FirstCharacter, Arlan, Asta, Bailu, Blade, Bronya, Clara, DanHeng, Gepard, Herta, Himeko, Hook, JingYuan, Luocha, March7th, Natasha, Pela, Qingque, Sampo, Seele, Serval, SilverWolf, Sushang, Tingyun, TrailblazerDestruction, TrailblazerPreservation, Welt, Yanqing, Yukong
|
||||
|
||||
# Group `DungeonDaily`
|
||||
DungeonDaily_CalyxGolden = 'Calyx_Golden_Treasures' # do_not_achieve, Calyx_Golden_Memories, Calyx_Golden_Aether, Calyx_Golden_Treasures
|
||||
DungeonDaily_CalyxCrimson = 'Calyx_Crimson_Erudition' # do_not_achieve, Calyx_Crimson_Destruction, Calyx_Crimson_Preservation, Calyx_Crimson_Hunt, Calyx_Crimson_Abundance, Calyx_Crimson_Erudition, Calyx_Crimson_Harmony, Calyx_Crimson_Nihility
|
||||
DungeonDaily_StagnantShadow = 'do_not_archive' # do_not_achieve, Stagnant_Shadow_Quanta, Stagnant_Shadow_Gust, Stagnant_Shadow_Fulmination, Stagnant_Shadow_Blaze, Stagnant_Shadow_Spike, Stagnant_Shadow_Rime, Stagnant_Shadow_Mirage, Stagnant_Shadow_Icicle, Stagnant_Shadow_Doom, Stagnant_Shadow_Celestial
|
||||
DungeonDaily_CavernOfCorrosion = 'Cavern_of_Corrosion_Path_of_Providence' # do_not_achieve, Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers
|
||||
|
||||
# Group `DungeonSupport`
|
||||
DungeonSupport_Use = 'when_daily' # always_use, when_daily, do_not_use
|
||||
DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Arlan, Asta, Bailu, Blade, Bronya, Clara, DanHeng, Gepard, Herta, Himeko, Hook, JingYuan, Kafka, Luka, Luocha, March7th, Natasha, Pela, Qingque, Sampo, Seele, Serval, SilverWolf, Sushang, Tingyun, TrailblazerDestruction, TrailblazerPreservation, Welt, Yanqing, Yukong
|
||||
|
||||
# Group `DungeonStorage`
|
||||
DungeonStorage_DungeonDouble = {}
|
||||
|
||||
# Group `AchievableQuest`
|
||||
AchievableQuest_Complete_1_Daily_Mission = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Clear_Calyx_Golden_1_times = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Complete_Calyx_Crimson_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Clear_Stagnant_Shadow_1_times = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Clear_Cavern_of_Corrosion_1_times = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_In_a_single_battle_inflict_3_Weakness_Break_of_different_Types = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Inflict_Weakness_Break_5_times = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Defeat_a_total_of_20_enemies = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Enter_combat_by_attacking_enemy_Weakness_and_win_3_times = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Use_Technique_2_times = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Go_on_assignment_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Take_1_photo = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Destroy_3_destructible_objects = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Complete_Forgotten_Hall_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Complete_Echo_of_War_1_times = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Complete_1_stage_in_Simulated_Universe_Any_world = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Obtain_victory_in_combat_with_support_characters_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Use_an_Ultimate_to_deal_the_final_blow_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Level_up_any_character_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Level_up_any_Light_Cone_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Level_up_any_Relic_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Salvage_any_Relic = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Synthesize_Consumable_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Synthesize_material_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
AchievableQuest_Use_Consumables_1_time = 'achievable' # achievable, not_set, not_supported
|
||||
|
||||
# Group `DailyStorage`
|
||||
DailyStorage_DailyActivity = {}
|
||||
DailyStorage_DailyQuest = {}
|
||||
|
||||
# Group `Assignment`
|
||||
Assignment_Duration = 20 # 4, 8, 12, 20
|
||||
|
@ -8,7 +8,7 @@ class ManualConfig:
|
||||
|
||||
SCHEDULER_PRIORITY = """
|
||||
Restart
|
||||
> Dungeon > Assignment > DailyQuest > BattlePass
|
||||
> DailyQuest > Dungeon > Assignment > BattlePass
|
||||
"""
|
||||
|
||||
"""
|
||||
|
@ -4,7 +4,7 @@ from cached_property import cached_property
|
||||
|
||||
from deploy.Windows.utils import DEPLOY_TEMPLATE, poor_yaml_read, poor_yaml_write
|
||||
from module.base.timer import timer
|
||||
from module.config.server import to_package, VALID_PACKAGE, VALID_CHANNEL_PACKAGE
|
||||
from module.config.server import VALID_CHANNEL_PACKAGE, VALID_PACKAGE, to_package
|
||||
from module.config.utils import *
|
||||
|
||||
CONFIG_IMPORT = '''
|
||||
@ -32,6 +32,11 @@ def gui_lang_to_ingame_lang(lang: str) -> str:
|
||||
return DICT_GUI_TO_INGAME.get(lang, 'en')
|
||||
|
||||
|
||||
def get_generator():
|
||||
from module.base.code_generator import CodeGenerator
|
||||
return CodeGenerator()
|
||||
|
||||
|
||||
class ConfigGenerator:
|
||||
@cached_property
|
||||
def argument(self):
|
||||
@ -47,6 +52,55 @@ class ConfigGenerator:
|
||||
"""
|
||||
data = {}
|
||||
raw = read_file(filepath_argument('argument'))
|
||||
|
||||
def option_add(keys, options):
|
||||
options = deep_get(raw, keys=keys, default=[]) + options
|
||||
deep_set(raw, keys=keys, value=options)
|
||||
|
||||
# Insert dungeons
|
||||
from tasks.dungeon.keywords import DungeonList
|
||||
option_add(
|
||||
keys='Dungeon.Name.option',
|
||||
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_daily_dungeon])
|
||||
# Double events
|
||||
option_add(
|
||||
keys='Dungeon.NameAtDoubleCalyx.option',
|
||||
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx])
|
||||
option_add(
|
||||
keys='Dungeon.NameAtDoubleRelic.option',
|
||||
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Cavern_of_Corrosion])
|
||||
# Dungeon daily
|
||||
option_add(
|
||||
keys='DungeonDaily.CalyxGolden.option',
|
||||
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden])
|
||||
option_add(
|
||||
keys='DungeonDaily.CalyxCrimson.option',
|
||||
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Crimson])
|
||||
option_add(
|
||||
keys='DungeonDaily.StagnantShadow.option',
|
||||
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Stagnant_Shadow])
|
||||
option_add(
|
||||
keys='DungeonDaily.CavernOfCorrosion.option',
|
||||
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Cavern_of_Corrosion])
|
||||
# Insert characters
|
||||
from tasks.character.keywords import CharacterList
|
||||
unsupported_characters = ["DanHengImbibitorLunae"]
|
||||
characters = [character.name for character in CharacterList.instances.values()
|
||||
if character.name not in unsupported_characters]
|
||||
option_add(keys='DungeonSupport.Character.option', options=characters)
|
||||
# Insert daily quests
|
||||
from tasks.daily.keywords import DailyQuest
|
||||
for quest in DailyQuest.instances.values():
|
||||
quest: DailyQuest
|
||||
deep_set(raw, keys=['AchievableQuest', quest.name], value={
|
||||
'type': 'state',
|
||||
'value': 'achievable',
|
||||
'option': ['achievable', 'not_set', 'not_supported'],
|
||||
'option_bold': ['achievable'],
|
||||
'option_light': ['not_supported'],
|
||||
})
|
||||
|
||||
# Load
|
||||
for path, value in deep_iter(raw, depth=2):
|
||||
arg = {
|
||||
'type': 'input',
|
||||
@ -56,6 +110,9 @@ class ConfigGenerator:
|
||||
if not isinstance(value, dict):
|
||||
value = {'value': value}
|
||||
arg['type'] = data_to_type(value, arg=path[1])
|
||||
if arg['type'] == 'stored':
|
||||
value['value'] = {}
|
||||
arg['display'] = 'hide' # Hide `stored` by default
|
||||
if isinstance(value['value'], datetime):
|
||||
arg['type'] = 'datetime'
|
||||
arg['validate'] = 'datetime'
|
||||
@ -63,14 +120,6 @@ class ConfigGenerator:
|
||||
arg.update(value)
|
||||
deep_set(data, keys=path, value=arg)
|
||||
|
||||
# Define storage group
|
||||
# arg = {
|
||||
# 'type': 'storage',
|
||||
# 'value': {},
|
||||
# 'valuetype': 'ignore',
|
||||
# 'display': 'disabled',
|
||||
# }
|
||||
# deep_set(data, keys=['Storage', 'Storage'], value=arg)
|
||||
return data
|
||||
|
||||
@cached_property
|
||||
@ -216,6 +265,28 @@ class ConfigGenerator:
|
||||
for text in lines:
|
||||
f.write(text + '\n')
|
||||
|
||||
@timer
|
||||
def generate_stored(self):
|
||||
import module.config.stored.classes as classes
|
||||
gen = get_generator()
|
||||
gen.add('from module.config.stored.classes import (')
|
||||
with gen.tab():
|
||||
for cls in sorted([name for name in dir(classes) if name.startswith('Stored')]):
|
||||
gen.add(cls + ',')
|
||||
gen.add(')')
|
||||
gen.Empty()
|
||||
gen.Empty()
|
||||
gen.Empty()
|
||||
gen.CommentAutoGenerage('module/config/config_updater.py')
|
||||
|
||||
with gen.Class('StoredGenerated'):
|
||||
for path, data in deep_iter(self.args, depth=3):
|
||||
cls = data.get('stored')
|
||||
if cls:
|
||||
gen.add(f'{path[-1]} = {cls}("{".".join(path)}")')
|
||||
|
||||
gen.write('module/config/stored/stored_generated.py')
|
||||
|
||||
@timer
|
||||
def generate_i18n(self, lang):
|
||||
"""
|
||||
@ -282,28 +353,65 @@ class ConfigGenerator:
|
||||
if dungeon.name in dailies:
|
||||
value = dungeon.__getattribute__(ingame_lang)
|
||||
deep_set(new, keys=['Dungeon', 'Name', dungeon.name], value=value)
|
||||
# Copy dungeon i18n to double events
|
||||
for dungeon in deep_get(new, keys='Dungeon.NameAtDoubleCalyx').values():
|
||||
if '_' in dungeon:
|
||||
value = deep_get(new, keys=['Dungeon', 'Name', dungeon])
|
||||
if value:
|
||||
deep_set(new, keys=['Dungeon', 'NameAtDoubleCalyx', dungeon], value=value)
|
||||
|
||||
# Copy dungeon i18n to double events
|
||||
def update_dungeon_names(keys):
|
||||
for dungeon in deep_get(new, keys=keys).values():
|
||||
if '_' in dungeon:
|
||||
value = deep_get(new, keys=['Dungeon', 'Name', dungeon])
|
||||
if value:
|
||||
deep_set(new, keys=f'{keys}.{dungeon}', value=value)
|
||||
|
||||
update_dungeon_names('Dungeon.NameAtDoubleCalyx')
|
||||
update_dungeon_names('Dungeon.NameAtDoubleRelic')
|
||||
update_dungeon_names('DungeonDaily.CalyxGolden')
|
||||
update_dungeon_names('DungeonDaily.CalyxCrimson')
|
||||
update_dungeon_names('DungeonDaily.StagnantShadow')
|
||||
update_dungeon_names('DungeonDaily.CavernOfCorrosion')
|
||||
|
||||
# Character names
|
||||
from tasks.character.keywords import CharacterList
|
||||
ingame_lang = gui_lang_to_ingame_lang(lang)
|
||||
characters = deep_get(self.argument, keys='Dungeon.SupportCharacter.option')
|
||||
characters = deep_get(self.argument, keys='DungeonSupport.Character.option')
|
||||
for character in CharacterList.instances.values():
|
||||
if character.name in characters:
|
||||
value = character.__getattribute__(ingame_lang)
|
||||
if "Trailblazer" in value:
|
||||
continue
|
||||
deep_set(new, keys=['Dungeon', 'SupportCharacter', character.name], value=value)
|
||||
deep_set(new, keys=['DungeonSupport', 'Character', character.name], value=value)
|
||||
|
||||
# Daily quests
|
||||
from tasks.daily.keywords import DailyQuest
|
||||
for quest in DailyQuest.instances.values():
|
||||
value = quest.__getattribute__(ingame_lang)
|
||||
deep_set(new, keys=['AchievableQuest', quest.name, 'name'], value=value)
|
||||
# deep_set(new, keys=['DailyQuest', quest.name, 'help'], value='')
|
||||
copy_from = 'Complete_1_Daily_Mission'
|
||||
if quest.name != copy_from:
|
||||
for option in deep_get(self.args, keys=['DailyQuest', 'AchievableQuest', copy_from, 'option']):
|
||||
value = deep_get(new, keys=['AchievableQuest', copy_from, option])
|
||||
deep_set(new, keys=['AchievableQuest', quest.name, option], value=value)
|
||||
|
||||
# GUI i18n
|
||||
for path, _ in deep_iter(self.gui, depth=2):
|
||||
group, key = path
|
||||
deep_load(keys=['Gui', group], words=(key,))
|
||||
|
||||
# zh-TW
|
||||
dic_repl = {
|
||||
'設置': '設定',
|
||||
'支持': '支援',
|
||||
'啓': '啟',
|
||||
'异': '異',
|
||||
'服務器': '伺服器',
|
||||
'文件': '檔案',
|
||||
}
|
||||
if lang == 'zh-TW':
|
||||
for path, value in deep_iter(new, depth=3):
|
||||
for before, after in dic_repl.items():
|
||||
value = value.replace(before, after)
|
||||
deep_set(new, keys=path, value=value)
|
||||
|
||||
write_file(filepath_i18n(lang), new)
|
||||
|
||||
@cached_property
|
||||
@ -366,25 +474,6 @@ class ConfigGenerator:
|
||||
# update('template-docker', docker)
|
||||
# update('template-docker-cn', docker, cn)
|
||||
|
||||
def insert_dungeon(self):
|
||||
from tasks.dungeon.keywords import DungeonList
|
||||
dungeons = [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_daily_dungeon]
|
||||
deep_set(self.argument, keys='Dungeon.Name.option', value=dungeons)
|
||||
deep_set(self.args, keys='Dungeon.Dungeon.Name.option', value=dungeons)
|
||||
|
||||
from tasks.character.keywords import CharacterList
|
||||
unsupported_characters = ["DanHengImbibitorLunae", 'Kafka', 'Luka']
|
||||
characters = ['FirstCharacter'] + [character.name for character in CharacterList.instances.values() if
|
||||
character.name not in unsupported_characters]
|
||||
deep_set(self.argument, keys='Dungeon.SupportCharacter.option', value=characters)
|
||||
deep_set(self.args, keys='Dungeon.Dungeon.SupportCharacter.option', value=characters)
|
||||
|
||||
dungeons = deep_get(self.argument, keys='Dungeon.NameAtDoubleCalyx.option')
|
||||
dungeons += [dungeon.name for dungeon in DungeonList.instances.values()
|
||||
if dungeon.is_Calyx_Golden or dungeon.is_Calyx_Crimson]
|
||||
deep_set(self.argument, keys='Dungeon.NameAtDoubleCalyx.option', value=dungeons)
|
||||
deep_set(self.args, keys='Dungeon.Dungeon.NameAtDoubleCalyx.option', value=dungeons)
|
||||
|
||||
def insert_assignment(self):
|
||||
from tasks.assignment.keywords import AssignmentEntry
|
||||
assignments = [entry.name for entry in AssignmentEntry.instances.values()]
|
||||
@ -404,13 +493,13 @@ class ConfigGenerator:
|
||||
_ = self.args
|
||||
_ = self.menu
|
||||
# _ = self.event
|
||||
self.insert_dungeon()
|
||||
self.insert_assignment()
|
||||
self.insert_package()
|
||||
# self.insert_server()
|
||||
write_file(filepath_args(), self.args)
|
||||
write_file(filepath_args('menu'), self.menu)
|
||||
self.generate_code()
|
||||
self.generate_stored()
|
||||
for lang in LANGUAGES:
|
||||
self.generate_i18n(lang)
|
||||
self.generate_deploy_template()
|
||||
@ -419,6 +508,8 @@ class ConfigGenerator:
|
||||
class ConfigUpdater:
|
||||
# source, target, (optional)convert_func
|
||||
redirection = [
|
||||
('Dungeon.Dungeon.Support', 'Dungeon.DungeonSupport.Use'),
|
||||
('Dungeon.Dungeon.SupportCharacter', 'Dungeon.DungeonSupport.Character'),
|
||||
]
|
||||
|
||||
@cached_property
|
||||
@ -439,7 +530,9 @@ class ConfigUpdater:
|
||||
def deep_load(keys):
|
||||
data = deep_get(self.args, keys=keys, default={})
|
||||
value = deep_get(old, keys=keys, default=data['value'])
|
||||
if is_template or value is None or value == '' or data['type'] == 'lock' or data.get('display') == 'hide':
|
||||
typ = data['type']
|
||||
display = data.get('display')
|
||||
if is_template or value is None or value == '' or typ == 'lock' or (display == 'hide' and typ != 'stored'):
|
||||
value = data['value']
|
||||
value = parse_value(value, data=data)
|
||||
deep_set(new, keys=keys, value=value)
|
||||
@ -449,6 +542,7 @@ class ConfigUpdater:
|
||||
|
||||
if not is_template:
|
||||
new = self.config_redirect(old, new)
|
||||
new = self.update_state(new)
|
||||
|
||||
return new
|
||||
|
||||
@ -501,6 +595,53 @@ class ConfigUpdater:
|
||||
|
||||
return new
|
||||
|
||||
@staticmethod
|
||||
def update_state(data):
|
||||
def set_daily(quest, value):
|
||||
if value is True:
|
||||
value = 'achievable'
|
||||
if value is False:
|
||||
value = 'not_set'
|
||||
deep_set(data, keys=['DailyQuest', 'AchievableQuest', quest], value=value)
|
||||
|
||||
set_daily('Complete_1_Daily_Mission', 'not_supported')
|
||||
# Dungeon
|
||||
dungeon = deep_get(data, keys='Dungeon.Scheduler.Enable')
|
||||
set_daily('Clear_Calyx_Golden_1_times',
|
||||
dungeon and deep_get(data, 'Dungeon.DungeonDaily.CalyxGolden') != 'do_not_achieve')
|
||||
set_daily('Complete_Calyx_Crimson_1_time',
|
||||
dungeon and deep_get(data, 'Dungeon.DungeonDaily.CalyxCrimson') != 'do_not_achieve')
|
||||
set_daily('Clear_Stagnant_Shadow_1_times',
|
||||
dungeon and deep_get(data, 'Dungeon.DungeonDaily.StagnantShadow') != 'do_not_achieve')
|
||||
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('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('Complete_Forgotten_Hall_1_time', 'not_supported')
|
||||
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')
|
||||
# Build
|
||||
set_daily('Level_up_any_character_1_time', 'not_supported')
|
||||
set_daily('Level_up_any_Light_Cone_1_time', 'not_supported')
|
||||
set_daily('Level_up_any_Relic_1_time', 'not_supported')
|
||||
# Items
|
||||
set_daily('Salvage_any_Relic', 'achievable')
|
||||
set_daily('Synthesize_Consumable_1_time', 'achievable')
|
||||
set_daily('Synthesize_material_1_time', 'achievable')
|
||||
set_daily('Use_Consumables_1_time', 'achievable')
|
||||
return data
|
||||
|
||||
def read_file(self, config_name, is_template=False):
|
||||
"""
|
||||
Read and update config file.
|
||||
|
@ -234,6 +234,18 @@
|
||||
"Calyx_Crimson_Harmony": "Trace: Harmony (Bud of Harmony)",
|
||||
"Calyx_Crimson_Nihility": "Trace: Nihility (Bud of Nihility)"
|
||||
},
|
||||
"NameAtDoubleRelic": {
|
||||
"name": "At Double Relic Event, choose dungeon",
|
||||
"help": "Return to the default dungeon settings after double times exhausted",
|
||||
"do_not_participate": "Dont participate in event",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "Relics: Ice Set & Wind Set (Path of Gelid Wind)",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "Relics: Physical Set & Break Effect Set (Path of Jabbing Punch)",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "Relics: Healing Set & Musketeer Set (Path of Drifting)",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "Relics: Guard Set & Quantum Set (Path of Providence)",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "Relics: DEF Set & Lighting Set (Path of Holy Hymn)",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "Relics: Fire Set & Imaginary Set (Path of Conflagration)",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "Relics: HP Set & SPD Set (Path of Elixir Seekers)"
|
||||
},
|
||||
"Team": {
|
||||
"name": "Dungeon Team",
|
||||
"help": "",
|
||||
@ -243,18 +255,77 @@
|
||||
"4": "4",
|
||||
"5": "5",
|
||||
"6": "6"
|
||||
}
|
||||
},
|
||||
"DungeonDaily": {
|
||||
"_info": {
|
||||
"name": "Daily Quest Settings",
|
||||
"help": "Clear required dungeon once to achieve daily quests"
|
||||
},
|
||||
"Support": {
|
||||
"name": "Enable buddy support",
|
||||
"help": "Whether to enable buddy support",
|
||||
"do_not_use": "do_not_use",
|
||||
"always_use": "always_use",
|
||||
"when_daily": "when_daily"
|
||||
"CalyxGolden": {
|
||||
"name": "Clear Calyx Golden 1 times",
|
||||
"help": "",
|
||||
"do_not_achieve": "Don't Do This Quest",
|
||||
"Calyx_Golden_Memories": "Material: Character EXP (Bud of Memories)",
|
||||
"Calyx_Golden_Aether": "Material: Light Cone EXP (Bud of Aether)",
|
||||
"Calyx_Golden_Treasures": "Material: Credit (Bud of Treasures)"
|
||||
},
|
||||
"SupportCharacter": {
|
||||
"name": "Dungeon.SupportCharacter.name",
|
||||
"help": "Dungeon.SupportCharacter.help",
|
||||
"FirstCharacter": "FirstCharacter",
|
||||
"CalyxCrimson": {
|
||||
"name": "Clear Calyx Crimson 1 times",
|
||||
"help": "",
|
||||
"do_not_achieve": "Don't Do This Quest",
|
||||
"Calyx_Crimson_Destruction": "Trace: Destruction (Bud of Destruction)",
|
||||
"Calyx_Crimson_Preservation": "Trace: Preservation (Bud of Preservation)",
|
||||
"Calyx_Crimson_Hunt": "Trace: Hunt (Bud of Hunt)",
|
||||
"Calyx_Crimson_Abundance": "Trace: Abundance (Bud of Abundance)",
|
||||
"Calyx_Crimson_Erudition": "Trace: Erudition (Bud of Erudition)",
|
||||
"Calyx_Crimson_Harmony": "Trace: Harmony (Bud of Harmony)",
|
||||
"Calyx_Crimson_Nihility": "Trace: Nihility (Bud of Nihility)"
|
||||
},
|
||||
"StagnantShadow": {
|
||||
"name": "Clear Stagnant Shadow 1 times",
|
||||
"help": "",
|
||||
"do_not_achieve": "Don't Do This Quest",
|
||||
"Stagnant_Shadow_Quanta": "Ascension: Quantum (Shape of Quanta)",
|
||||
"Stagnant_Shadow_Gust": "Ascension: Wind (Shape of Gust)",
|
||||
"Stagnant_Shadow_Fulmination": "Ascension: Lighting (Shape of Fulmination)",
|
||||
"Stagnant_Shadow_Blaze": "Ascension: Fire (Shape of Blaze)",
|
||||
"Stagnant_Shadow_Spike": "Ascension: Physical (Shape of Spike)",
|
||||
"Stagnant_Shadow_Rime": "Ascension: Ice (Shape of Rime)",
|
||||
"Stagnant_Shadow_Mirage": "Ascension: Imaginary (Shape of Mirage)",
|
||||
"Stagnant_Shadow_Icicle": "Ascension: Ice (Shape of Icicle)",
|
||||
"Stagnant_Shadow_Doom": "Ascension: Lighting (Shape of Doom)",
|
||||
"Stagnant_Shadow_Celestial": "Ascension: Wind (Shape of Celestial)"
|
||||
},
|
||||
"CavernOfCorrosion": {
|
||||
"name": "Clear Cavern of Corrosion 1 times",
|
||||
"help": "",
|
||||
"do_not_achieve": "Don't Do This Quest",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "Relics: Ice Set & Wind Set (Path of Gelid Wind)",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "Relics: Physical Set & Break Effect Set (Path of Jabbing Punch)",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "Relics: Healing Set & Musketeer Set (Path of Drifting)",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "Relics: Guard Set & Quantum Set (Path of Providence)",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "Relics: DEF Set & Lighting Set (Path of Holy Hymn)",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "Relics: Fire Set & Imaginary Set (Path of Conflagration)",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "Relics: HP Set & SPD Set (Path of Elixir Seekers)"
|
||||
}
|
||||
},
|
||||
"DungeonSupport": {
|
||||
"_info": {
|
||||
"name": "Support Settings",
|
||||
"help": ""
|
||||
},
|
||||
"Use": {
|
||||
"name": "Use Friend Support",
|
||||
"help": "",
|
||||
"always_use": "Always Use",
|
||||
"when_daily": "Use Only When Required by Dailies",
|
||||
"do_not_use": "Don't Use"
|
||||
},
|
||||
"Character": {
|
||||
"name": "Support Character",
|
||||
"help": "Select a friend support character, if not found, select the default (first) role",
|
||||
"FirstCharacter": "First Character",
|
||||
"Arlan": "Arlan",
|
||||
"Asta": "Asta",
|
||||
"Bailu": "Bailu",
|
||||
@ -267,6 +338,8 @@
|
||||
"Himeko": "Himeko",
|
||||
"Hook": "Hook",
|
||||
"JingYuan": "Jing Yuan",
|
||||
"Kafka": "Kafka",
|
||||
"Luka": "Luka",
|
||||
"Luocha": "Luocha",
|
||||
"March7th": "March 7th",
|
||||
"Natasha": "Natasha",
|
||||
@ -278,13 +351,218 @@
|
||||
"SilverWolf": "Silver Wolf",
|
||||
"Sushang": "Sushang",
|
||||
"Tingyun": "Tingyun",
|
||||
"TrailblazerDestruction": "TrailblazerDestruction",
|
||||
"TrailblazerPreservation": "TrailblazerPreservation",
|
||||
"TrailblazerDestruction": "Trailblazer Destruction",
|
||||
"TrailblazerPreservation": "Trailblazer Preservation",
|
||||
"Welt": "Welt",
|
||||
"Yanqing": "Yanqing",
|
||||
"Yukong": "Yukong"
|
||||
}
|
||||
},
|
||||
"DungeonStorage": {
|
||||
"_info": {
|
||||
"name": "DungeonStorage._info.name",
|
||||
"help": "DungeonStorage._info.help"
|
||||
},
|
||||
"DungeonDouble": {
|
||||
"name": "DungeonStorage.DungeonDouble.name",
|
||||
"help": "DungeonStorage.DungeonDouble.help"
|
||||
}
|
||||
},
|
||||
"AchievableQuest": {
|
||||
"_info": {
|
||||
"name": "Achievable Quests",
|
||||
"help": "When the task status is \"Not Set\", you need to configure the SRC as required to achieve the quest\nNote: Please keep more tasks in \"Achievable\" status, otherwise SRC may not be able to grind 500 activity"
|
||||
},
|
||||
"Complete_1_Daily_Mission": {
|
||||
"name": "Complete 1 Daily Mission",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Clear_Calyx_Golden_1_times": {
|
||||
"name": "Clear Calyx (Golden) 1 time(s)",
|
||||
"help": "Need to configure and enable the \"Dungeon\" task",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Complete_Calyx_Crimson_1_time": {
|
||||
"name": "Complete Calyx (Crimson) 1 time",
|
||||
"help": "Need to configure and enable the \"Dungeon\" task",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Clear_Stagnant_Shadow_1_times": {
|
||||
"name": "Clear Stagnant Shadow 1 time(s)",
|
||||
"help": "Need to configure and enable the \"Dungeon\" task",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Clear_Cavern_of_Corrosion_1_times": {
|
||||
"name": "Clear Cavern of Corrosion 1 time(s)",
|
||||
"help": "Need to configure and enable the \"Dungeon\" task",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"name": "In a single battle, inflict 3 Weakness Break of different Types",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"name": "Inflict Weakness Break 5 times",
|
||||
"help": "",
|
||||
"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": "",
|
||||
"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": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Use_Technique_2_times": {
|
||||
"name": "Use Technique 2 times",
|
||||
"help": "Achievable by default, will go to the abyssal 1 and use technique twice",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Go_on_assignment_1_time": {
|
||||
"name": "Go on assignment 1 time",
|
||||
"help": "Need to configure and enable the \"Assignment\" task",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Take_1_photo": {
|
||||
"name": "Take 1 photo",
|
||||
"help": "Achievable by default",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"name": "Destroy 3 destructible objects",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"name": "Complete Forgotten Hall 1 time",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Complete_Echo_of_War_1_times": {
|
||||
"name": "Complete Echo of War 1 time(s)",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Complete_1_stage_in_Simulated_Universe_Any_world": {
|
||||
"name": "Complete 1 stage in Simulated Universe (Any world)",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Obtain_victory_in_combat_with_support_characters_1_time": {
|
||||
"name": "Obtain victory in combat with support characters 1 time",
|
||||
"help": "Need to configure and enable the \"Dungeon\" task, configure support settings also",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"name": "Use an Ultimate to deal the final blow 1 time",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Level_up_any_character_1_time": {
|
||||
"name": "Level up any character 1 time",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Level_up_any_Light_Cone_1_time": {
|
||||
"name": "Level up any Light Cone 1 time",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Level_up_any_Relic_1_time": {
|
||||
"name": "Level up any Relic 1 time",
|
||||
"help": "",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Salvage_any_Relic": {
|
||||
"name": "Salvage any Relic",
|
||||
"help": "Achievable by default, will salvage the first one in reverse order of rarity",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Synthesize_Consumable_1_time": {
|
||||
"name": "Synthesize Consumable 1 time",
|
||||
"help": "Achievable by default, will synthesize low-rarity snacks",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Synthesize_material_1_time": {
|
||||
"name": "Synthesize material 1 time",
|
||||
"help": "Achievable by default, will synthesize low-rarity material",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
},
|
||||
"Use_Consumables_1_time": {
|
||||
"name": "Use Consumables 1 time",
|
||||
"help": "Achievable by default, will use gear. If there is no material, synthesized before use",
|
||||
"achievable": "Achievable",
|
||||
"not_set": "Not Set",
|
||||
"not_supported": "Not Supported Yet"
|
||||
}
|
||||
},
|
||||
"DailyStorage": {
|
||||
"_info": {
|
||||
"name": "DailyStorage._info.name",
|
||||
"help": "DailyStorage._info.help"
|
||||
},
|
||||
"DailyActivity": {
|
||||
"name": "DailyStorage.DailyActivity.name",
|
||||
"help": "DailyStorage.DailyActivity.help"
|
||||
},
|
||||
"DailyQuest": {
|
||||
"name": "DailyStorage.DailyQuest.name",
|
||||
"help": "DailyStorage.DailyQuest.help"
|
||||
}
|
||||
},
|
||||
"Assignment": {
|
||||
"_info": {
|
||||
"name": "Assignment Settings",
|
||||
|
@ -234,6 +234,18 @@
|
||||
"Calyx_Crimson_Harmony": "疑似花萼(赤)・調和の蕾",
|
||||
"Calyx_Crimson_Nihility": "疑似花萼(赤)・虚無の蕾"
|
||||
},
|
||||
"NameAtDoubleRelic": {
|
||||
"name": "Dungeon.NameAtDoubleRelic.name",
|
||||
"help": "Dungeon.NameAtDoubleRelic.help",
|
||||
"do_not_participate": "do_not_participate",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "侵蝕トンネル・霜風の路",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "侵蝕トンネル・迅拳の路",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "侵蝕トンネル・漂泊の路",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "侵蝕トンネル・睿治の路",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "侵蝕トンネル・聖頌の路",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "侵蝕トンネル・野焔の路",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "侵蝕トンネル・薬使の路"
|
||||
},
|
||||
"Team": {
|
||||
"name": "Dungeon.Team.name",
|
||||
"help": "Dungeon.Team.help",
|
||||
@ -243,17 +255,76 @@
|
||||
"4": "4",
|
||||
"5": "5",
|
||||
"6": "6"
|
||||
}
|
||||
},
|
||||
"DungeonDaily": {
|
||||
"_info": {
|
||||
"name": "DungeonDaily._info.name",
|
||||
"help": "DungeonDaily._info.help"
|
||||
},
|
||||
"Support": {
|
||||
"name": "Dungeon.Support.name",
|
||||
"help": "Dungeon.Support.help",
|
||||
"do_not_use": "do_not_use",
|
||||
"CalyxGolden": {
|
||||
"name": "DungeonDaily.CalyxGolden.name",
|
||||
"help": "DungeonDaily.CalyxGolden.help",
|
||||
"do_not_achieve": "do_not_achieve",
|
||||
"Calyx_Golden_Memories": "疑似花萼(金)・回憶の蕾",
|
||||
"Calyx_Golden_Aether": "疑似花萼(金)・エーテルの蕾",
|
||||
"Calyx_Golden_Treasures": "疑似花萼(金)・秘蔵の蕾"
|
||||
},
|
||||
"CalyxCrimson": {
|
||||
"name": "DungeonDaily.CalyxCrimson.name",
|
||||
"help": "DungeonDaily.CalyxCrimson.help",
|
||||
"do_not_achieve": "do_not_achieve",
|
||||
"Calyx_Crimson_Destruction": "疑似花萼(赤)・壊滅の蕾",
|
||||
"Calyx_Crimson_Preservation": "疑似花萼(赤)・存護の蕾",
|
||||
"Calyx_Crimson_Hunt": "疑似花萼(赤)・巡狩の蕾",
|
||||
"Calyx_Crimson_Abundance": "疑似花萼(赤)・豊穣の蕾",
|
||||
"Calyx_Crimson_Erudition": "疑似花萼(赤)・知恵の蕾",
|
||||
"Calyx_Crimson_Harmony": "疑似花萼(赤)・調和の蕾",
|
||||
"Calyx_Crimson_Nihility": "疑似花萼(赤)・虚無の蕾"
|
||||
},
|
||||
"StagnantShadow": {
|
||||
"name": "DungeonDaily.StagnantShadow.name",
|
||||
"help": "DungeonDaily.StagnantShadow.help",
|
||||
"do_not_achieve": "do_not_achieve",
|
||||
"Stagnant_Shadow_Quanta": "凝結虚影・虚海の形",
|
||||
"Stagnant_Shadow_Gust": "凝結虚影・薫風の形",
|
||||
"Stagnant_Shadow_Fulmination": "凝結虚影・鳴雷の形",
|
||||
"Stagnant_Shadow_Blaze": "凝結虚影・炎華の形",
|
||||
"Stagnant_Shadow_Spike": "凝結虚影・切先の形",
|
||||
"Stagnant_Shadow_Rime": "凝結虚影・霜晶の形",
|
||||
"Stagnant_Shadow_Mirage": "凝結虚影・幻光の形",
|
||||
"Stagnant_Shadow_Icicle": "凝結虚影・氷柱の形",
|
||||
"Stagnant_Shadow_Doom": "凝結虚影・震厄の形",
|
||||
"Stagnant_Shadow_Celestial": "凝結虚影・天人の形"
|
||||
},
|
||||
"CavernOfCorrosion": {
|
||||
"name": "DungeonDaily.CavernOfCorrosion.name",
|
||||
"help": "DungeonDaily.CavernOfCorrosion.help",
|
||||
"do_not_achieve": "do_not_achieve",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "侵蝕トンネル・霜風の路",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "侵蝕トンネル・迅拳の路",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "侵蝕トンネル・漂泊の路",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "侵蝕トンネル・睿治の路",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "侵蝕トンネル・聖頌の路",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "侵蝕トンネル・野焔の路",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "侵蝕トンネル・薬使の路"
|
||||
}
|
||||
},
|
||||
"DungeonSupport": {
|
||||
"_info": {
|
||||
"name": "DungeonSupport._info.name",
|
||||
"help": "DungeonSupport._info.help"
|
||||
},
|
||||
"Use": {
|
||||
"name": "DungeonSupport.Use.name",
|
||||
"help": "DungeonSupport.Use.help",
|
||||
"always_use": "always_use",
|
||||
"when_daily": "when_daily"
|
||||
"when_daily": "when_daily",
|
||||
"do_not_use": "do_not_use"
|
||||
},
|
||||
"SupportCharacter": {
|
||||
"name": "Dungeon.SupportCharacter.name",
|
||||
"help": "Dungeon.SupportCharacter.help",
|
||||
"Character": {
|
||||
"name": "DungeonSupport.Character.name",
|
||||
"help": "DungeonSupport.Character.help",
|
||||
"FirstCharacter": "FirstCharacter",
|
||||
"Arlan": "アーラン",
|
||||
"Asta": "アスター",
|
||||
@ -267,6 +338,8 @@
|
||||
"Himeko": "姫子",
|
||||
"Hook": "フック",
|
||||
"JingYuan": "景元",
|
||||
"Kafka": "カフカ",
|
||||
"Luka": "ルカ",
|
||||
"Luocha": "羅刹",
|
||||
"March7th": "三月なのか",
|
||||
"Natasha": "ナターシャ",
|
||||
@ -285,6 +358,211 @@
|
||||
"Yukong": "御空"
|
||||
}
|
||||
},
|
||||
"DungeonStorage": {
|
||||
"_info": {
|
||||
"name": "DungeonStorage._info.name",
|
||||
"help": "DungeonStorage._info.help"
|
||||
},
|
||||
"DungeonDouble": {
|
||||
"name": "DungeonStorage.DungeonDouble.name",
|
||||
"help": "DungeonStorage.DungeonDouble.help"
|
||||
}
|
||||
},
|
||||
"AchievableQuest": {
|
||||
"_info": {
|
||||
"name": "AchievableQuest._info.name",
|
||||
"help": "AchievableQuest._info.help"
|
||||
},
|
||||
"Complete_1_Daily_Mission": {
|
||||
"name": "デイリークエストを1回クリアする",
|
||||
"help": "AchievableQuest.Complete_1_Daily_Mission.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Clear_Calyx_Golden_1_times": {
|
||||
"name": "「疑似花萼(金)」を1回クリアする",
|
||||
"help": "AchievableQuest.Clear_Calyx_Golden_1_times.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Complete_Calyx_Crimson_1_time": {
|
||||
"name": "「疑似花萼(赤)」を1回クリアする",
|
||||
"help": "AchievableQuest.Complete_Calyx_Crimson_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Clear_Stagnant_Shadow_1_times": {
|
||||
"name": "「凝結虚影」を1回クリアする",
|
||||
"help": "AchievableQuest.Clear_Stagnant_Shadow_1_times.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Clear_Cavern_of_Corrosion_1_times": {
|
||||
"name": "「侵蝕トンネル」を1回クリアする",
|
||||
"help": "AchievableQuest.Clear_Cavern_of_Corrosion_1_times.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"name": "一度の戦闘で、異なる3種の属性の弱点撃破を発動する",
|
||||
"help": "AchievableQuest.In_a_single_battle_inflict_3_Weakness_Break_of_different_Types.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"name": "累計で弱点撃破効果を5回発動する",
|
||||
"help": "AchievableQuest.Inflict_Weakness_Break_5_times.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Defeat_a_total_of_20_enemies": {
|
||||
"name": "敵を累計で20体倒す",
|
||||
"help": "AchievableQuest.Defeat_a_total_of_20_enemies.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": {
|
||||
"name": "弱点を攻撃して戦闘に入り、3回勝利する",
|
||||
"help": "AchievableQuest.Enter_combat_by_attacking_enemy_Weakness_and_win_3_times.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Use_Technique_2_times": {
|
||||
"name": "秘技を累計2回発動する",
|
||||
"help": "AchievableQuest.Use_Technique_2_times.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Go_on_assignment_1_time": {
|
||||
"name": "依頼に1回派遣する",
|
||||
"help": "AchievableQuest.Go_on_assignment_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Take_1_photo": {
|
||||
"name": "1回撮影する",
|
||||
"help": "AchievableQuest.Take_1_photo.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"name": "破壊できるオブジェクトを累計で3つ破壊する",
|
||||
"help": "AchievableQuest.Destroy_3_destructible_objects.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"name": "「忘却の庭」を1回クリアする",
|
||||
"help": "AchievableQuest.Complete_Forgotten_Hall_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Complete_Echo_of_War_1_times": {
|
||||
"name": "「歴戦余韻」を1回クリアする",
|
||||
"help": "AchievableQuest.Complete_Echo_of_War_1_times.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Complete_1_stage_in_Simulated_Universe_Any_world": {
|
||||
"name": "「模擬宇宙」のエリアを1つクリアする(任意の世界)",
|
||||
"help": "AchievableQuest.Complete_1_stage_in_Simulated_Universe_Any_world.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Obtain_victory_in_combat_with_support_characters_1_time": {
|
||||
"name": "サポートキャラを使い、戦闘に1回勝利する",
|
||||
"help": "AchievableQuest.Obtain_victory_in_combat_with_support_characters_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"name": "必殺技で最後の一撃を1回与える",
|
||||
"help": "AchievableQuest.Use_an_Ultimate_to_deal_the_final_blow_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Level_up_any_character_1_time": {
|
||||
"name": "任意のキャラを1回レベルアップする",
|
||||
"help": "AchievableQuest.Level_up_any_character_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Level_up_any_Light_Cone_1_time": {
|
||||
"name": "任意の光円錐を1回レベルアップする",
|
||||
"help": "AchievableQuest.Level_up_any_Light_Cone_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Level_up_any_Relic_1_time": {
|
||||
"name": "任意の遺物を1回レベルアップする",
|
||||
"help": "AchievableQuest.Level_up_any_Relic_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Salvage_any_Relic": {
|
||||
"name": "任意の遺物1つを分解する",
|
||||
"help": "AchievableQuest.Salvage_any_Relic.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Synthesize_Consumable_1_time": {
|
||||
"name": "消耗品を1回合成する",
|
||||
"help": "AchievableQuest.Synthesize_Consumable_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Synthesize_material_1_time": {
|
||||
"name": "素材を1回合成する",
|
||||
"help": "AchievableQuest.Synthesize_material_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
},
|
||||
"Use_Consumables_1_time": {
|
||||
"name": "消耗品を1個使う",
|
||||
"help": "AchievableQuest.Use_Consumables_1_time.help",
|
||||
"achievable": "achievable",
|
||||
"not_set": "not_set",
|
||||
"not_supported": "not_supported"
|
||||
}
|
||||
},
|
||||
"DailyStorage": {
|
||||
"_info": {
|
||||
"name": "DailyStorage._info.name",
|
||||
"help": "DailyStorage._info.help"
|
||||
},
|
||||
"DailyActivity": {
|
||||
"name": "DailyStorage.DailyActivity.name",
|
||||
"help": "DailyStorage.DailyActivity.help"
|
||||
},
|
||||
"DailyQuest": {
|
||||
"name": "DailyStorage.DailyQuest.name",
|
||||
"help": "DailyStorage.DailyQuest.help"
|
||||
}
|
||||
},
|
||||
"Assignment": {
|
||||
"_info": {
|
||||
"name": "依頼設定",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"help": ""
|
||||
},
|
||||
"Assignment": {
|
||||
"name": "委托设置",
|
||||
"name": "委托",
|
||||
"help": ""
|
||||
},
|
||||
"Rogue": {
|
||||
@ -217,7 +217,7 @@
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "遗器:铁卫套+量子套(睿治之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "遗器:防御套+雷套(圣颂之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "遗器:火套+虚数套(野焰之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遗器:生命套+速度套 (药使之径•侵蚀隧洞)"
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遗器:生命套+速度套(药使之径•侵蚀隧洞)"
|
||||
},
|
||||
"NameAtDoubleCalyx": {
|
||||
"name": "有双倍花活动时,选择副本",
|
||||
@ -234,6 +234,18 @@
|
||||
"Calyx_Crimson_Harmony": "行迹材料:同谐(同谐之蕾•拟造花萼赤)",
|
||||
"Calyx_Crimson_Nihility": "行迹材料:虚无(虚无之蕾•拟造花萼赤)"
|
||||
},
|
||||
"NameAtDoubleRelic": {
|
||||
"name": "有遗器活动时,选择副本",
|
||||
"help": "次数耗尽后回退到默认打本设置",
|
||||
"do_not_participate": "不参与活动",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "遗器:冰套+风套(霜风之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遗器:物理套+击破套(迅拳之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "遗器:治疗套+快枪手(漂泊之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "遗器:铁卫套+量子套(睿治之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "遗器:防御套+雷套(圣颂之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "遗器:火套+虚数套(野焰之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遗器:生命套+速度套(药使之径•侵蚀隧洞)"
|
||||
},
|
||||
"Team": {
|
||||
"name": "打本队伍",
|
||||
"help": "",
|
||||
@ -243,15 +255,74 @@
|
||||
"4": "4",
|
||||
"5": "5",
|
||||
"6": "6"
|
||||
}
|
||||
},
|
||||
"DungeonDaily": {
|
||||
"_info": {
|
||||
"name": "每日任务设置",
|
||||
"help": "打一次特定的本,以满足每日任务的要求"
|
||||
},
|
||||
"Support": {
|
||||
"name": "启用好友支援",
|
||||
"help": "是否启用好友支援",
|
||||
"do_not_use": "否",
|
||||
"always_use": "是",
|
||||
"when_daily": "仅当每日任务需要时"
|
||||
"CalyxGolden": {
|
||||
"name": "完成1次拟造花萼(金)",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成这个任务",
|
||||
"Calyx_Golden_Memories": "材料:角色经验(回忆之蕾•拟造花萼金)",
|
||||
"Calyx_Golden_Aether": "材料:武器经验(以太之蕾•拟造花萼金)",
|
||||
"Calyx_Golden_Treasures": "材料:信用点(藏珍之蕾•拟造花萼金)"
|
||||
},
|
||||
"SupportCharacter": {
|
||||
"CalyxCrimson": {
|
||||
"name": "完成1次拟造花萼(赤)",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成这个任务",
|
||||
"Calyx_Crimson_Destruction": "行迹材料:毁灭(毁灭之蕾•拟造花萼赤)",
|
||||
"Calyx_Crimson_Preservation": "行迹材料:存护(存护之蕾•拟造花萼赤)",
|
||||
"Calyx_Crimson_Hunt": "行迹材料:巡猎(存护之蕾•拟造花萼赤)",
|
||||
"Calyx_Crimson_Abundance": "行迹材料:丰饶(丰饶之蕾•拟造花萼赤)",
|
||||
"Calyx_Crimson_Erudition": "行迹材料:智识(智识之蕾•拟造花萼赤)",
|
||||
"Calyx_Crimson_Harmony": "行迹材料:同谐(同谐之蕾•拟造花萼赤)",
|
||||
"Calyx_Crimson_Nihility": "行迹材料:虚无(虚无之蕾•拟造花萼赤)"
|
||||
},
|
||||
"StagnantShadow": {
|
||||
"name": "完成1次凝滞虚影",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成这个任务",
|
||||
"Stagnant_Shadow_Quanta": "角色晋阶材料:量子(空海之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Gust": "角色晋阶材料:风(巽风之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Fulmination": "角色晋阶材料:雷(鸣雷之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Blaze": "角色晋阶材料:火(炎华之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Spike": "角色晋阶材料:物理(锋芒之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Rime": "角色晋阶材料:冰(霜晶之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Mirage": "角色晋阶材料:虚数(幻光之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Icicle": "角色晋阶材料:冰(冰棱之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Doom": "角色晋阶材料:雷(震厄之形•凝滞虚影)",
|
||||
"Stagnant_Shadow_Celestial": "角色晋阶材料:风(天人之形•凝滞虚影)"
|
||||
},
|
||||
"CavernOfCorrosion": {
|
||||
"name": "完成1次侵蚀隧洞",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成这个任务",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "遗器:冰套+风套(霜风之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遗器:物理套+击破套(迅拳之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "遗器:治疗套+快枪手(漂泊之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "遗器:铁卫套+量子套(睿治之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "遗器:防御套+雷套(圣颂之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "遗器:火套+虚数套(野焰之径•侵蚀隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遗器:生命套+速度套(药使之径•侵蚀隧洞)"
|
||||
}
|
||||
},
|
||||
"DungeonSupport": {
|
||||
"_info": {
|
||||
"name": "支援设置",
|
||||
"help": ""
|
||||
},
|
||||
"Use": {
|
||||
"name": "使用好友支援",
|
||||
"help": "",
|
||||
"always_use": "总是使用",
|
||||
"when_daily": "仅当每日任务需要时使用",
|
||||
"do_not_use": "不使用"
|
||||
},
|
||||
"Character": {
|
||||
"name": "好友支援角色",
|
||||
"help": "选择好友支援角色,未找到则选择默认(第一个)角色",
|
||||
"FirstCharacter": "支援列表第一个角色",
|
||||
@ -267,6 +338,8 @@
|
||||
"Himeko": "姬子",
|
||||
"Hook": "虎克",
|
||||
"JingYuan": "景元",
|
||||
"Kafka": "卡芙卡",
|
||||
"Luka": "卢卡",
|
||||
"Luocha": "罗刹",
|
||||
"March7th": "三月七",
|
||||
"Natasha": "娜塔莎",
|
||||
@ -285,6 +358,211 @@
|
||||
"Yukong": "驭空"
|
||||
}
|
||||
},
|
||||
"DungeonStorage": {
|
||||
"_info": {
|
||||
"name": "DungeonStorage._info.name",
|
||||
"help": "DungeonStorage._info.help"
|
||||
},
|
||||
"DungeonDouble": {
|
||||
"name": "DungeonStorage.DungeonDouble.name",
|
||||
"help": "DungeonStorage.DungeonDouble.help"
|
||||
}
|
||||
},
|
||||
"AchievableQuest": {
|
||||
"_info": {
|
||||
"name": "可完成的任务",
|
||||
"help": "任务状态为 \"未设置\" 时需要按照要求设置SRC,才能启用任务\n注意:请让更多的任务处于 \"可完成\" 状态,否则SRC可能无法完成500点的活跃度要求"
|
||||
},
|
||||
"Complete_1_Daily_Mission": {
|
||||
"name": "完成1个日常任务",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Clear_Calyx_Golden_1_times": {
|
||||
"name": "完成1次「拟造花萼(金)」",
|
||||
"help": "需要设置并启用\"每日副本\"任务",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Complete_Calyx_Crimson_1_time": {
|
||||
"name": "完成1次「拟造花萼(赤)」",
|
||||
"help": "需要设置并启用\"每日副本\"任务",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Clear_Stagnant_Shadow_1_times": {
|
||||
"name": "完成1次「凝滞虚影」",
|
||||
"help": "需要设置并启用\"每日副本\"任务",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Clear_Cavern_of_Corrosion_1_times": {
|
||||
"name": "完成1次「侵蚀隧洞」",
|
||||
"help": "需要设置并启用\"每日副本\"任务",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"name": "单场战斗中,触发3种不同属性的弱点击破",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"name": "累计触发弱点击破效果5次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Defeat_a_total_of_20_enemies": {
|
||||
"name": "累计消灭20个敌人",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": {
|
||||
"name": "利用弱点进入战斗并获胜3次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Use_Technique_2_times": {
|
||||
"name": "累计施放2次秘技",
|
||||
"help": "默认可完成,将前往深渊一施放2次秘技",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Go_on_assignment_1_time": {
|
||||
"name": "派遣1次委托",
|
||||
"help": "需要设置并启用\"委托\"任务",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Take_1_photo": {
|
||||
"name": "拍照1次",
|
||||
"help": "默认可完成",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"name": "累计击碎3个可破坏物",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"name": "完成1次「忘却之庭」",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Complete_Echo_of_War_1_times": {
|
||||
"name": "完成1次「历战余响」",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Complete_1_stage_in_Simulated_Universe_Any_world": {
|
||||
"name": "通关「模拟宇宙」(任意世界)的1个区域",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Obtain_victory_in_combat_with_support_characters_1_time": {
|
||||
"name": "使用支援角色并获得战斗胜利1次",
|
||||
"help": "需要设置并启用\"每日副本\",且设置好友支援",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"name": "施放终结技造成制胜一击1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Level_up_any_character_1_time": {
|
||||
"name": "将任意角色等级提升1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Level_up_any_Light_Cone_1_time": {
|
||||
"name": "将任意光锥等级提升1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Level_up_any_Relic_1_time": {
|
||||
"name": "将任意遗器等级提升1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Salvage_any_Relic": {
|
||||
"name": "分解任意1件遗器",
|
||||
"help": "默认可完成,将分解遗器稀有度倒序的第一个",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Synthesize_Consumable_1_time": {
|
||||
"name": "合成1次消耗品",
|
||||
"help": "默认可完成,将合成最低级零食",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Synthesize_material_1_time": {
|
||||
"name": "合成1次材料",
|
||||
"help": "默认可完成,将合成最低级材料",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
},
|
||||
"Use_Consumables_1_time": {
|
||||
"name": "使用1件消耗品",
|
||||
"help": "默认可完成,将使用护具,无材料时先合成再使用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未设置",
|
||||
"not_supported": "暂未支持"
|
||||
}
|
||||
},
|
||||
"DailyStorage": {
|
||||
"_info": {
|
||||
"name": "DailyStorage._info.name",
|
||||
"help": "DailyStorage._info.help"
|
||||
},
|
||||
"DailyActivity": {
|
||||
"name": "DailyStorage.DailyActivity.name",
|
||||
"help": "DailyStorage.DailyActivity.help"
|
||||
},
|
||||
"DailyQuest": {
|
||||
"name": "DailyStorage.DailyQuest.name",
|
||||
"help": "DailyStorage.DailyQuest.help"
|
||||
}
|
||||
},
|
||||
"Assignment": {
|
||||
"_info": {
|
||||
"name": "委托设置",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"help": ""
|
||||
},
|
||||
"Assignment": {
|
||||
"name": "委託設置",
|
||||
"name": "委託",
|
||||
"help": ""
|
||||
},
|
||||
"Rogue": {
|
||||
@ -72,7 +72,7 @@
|
||||
},
|
||||
"Serial": {
|
||||
"name": "模擬器 Serial",
|
||||
"help": "常見的模擬器 Serial 可以查詢下方列表\n填 \"auto\" 自動檢測模擬器,多個模擬器正在運行或使用不支持自動檢測的模擬器時無法使用 \"auto\",必須手動填寫\n模擬器預設 Serial:\n- 藍疊模擬器 127.0.0.1:5555\n- 藍疊模擬器4 Hyper-v版,填\"bluestacks4-hyperv\"自動連接,多開填\"bluestacks4-hyperv-2\"以此類推\n- 藍疊模擬器5 Hyper-v版,填\"bluestacks5-hyperv\"自動連接,多開填\"bluestacks5-hyperv-1\"以此類推\n- 夜神模擬器 127.0.0.1:62001\n- 夜神模擬器64位元 127.0.0.1:59865\n- MuMu模擬器/MuMu模擬器X 127.0.0.1:7555\n- MuMu模擬器12 127.0.0.1:16384\n- 逍遙模擬器 127.0.0.1:21503\n- 雷電模擬器 emulator-5554 或 127.0.0.1:5555\n- WSA,填\"wsa-0\"使遊戲在後臺運行,需要使用第三方軟件操控或關閉\n如果你使用了模擬器的多開功能,他們的 Serial 將不是預設的,可以在 console.bat 中執行 `adb devices` 查詢,或根據模擬器官方的教程填寫"
|
||||
"help": "常見的模擬器 Serial 可以查詢下方列表\n填 \"auto\" 自動檢測模擬器,多個模擬器正在運行或使用不支援自動檢測的模擬器時無法使用 \"auto\",必須手動填寫\n模擬器預設 Serial:\n- 藍疊模擬器 127.0.0.1:5555\n- 藍疊模擬器4 Hyper-v版,填\"bluestacks4-hyperv\"自動連接,多開填\"bluestacks4-hyperv-2\"以此類推\n- 藍疊模擬器5 Hyper-v版,填\"bluestacks5-hyperv\"自動連接,多開填\"bluestacks5-hyperv-1\"以此類推\n- 夜神模擬器 127.0.0.1:62001\n- 夜神模擬器64位元 127.0.0.1:59865\n- MuMu模擬器/MuMu模擬器X 127.0.0.1:7555\n- MuMu模擬器12 127.0.0.1:16384\n- 逍遙模擬器 127.0.0.1:21503\n- 雷電模擬器 emulator-5554 或 127.0.0.1:5555\n- WSA,填\"wsa-0\"使遊戲在後臺運行,需要使用第三方軟件操控或關閉\n如果你使用了模擬器的多開功能,他們的 Serial 將不是預設的,可以在 console.bat 中執行 `adb devices` 查詢,或根據模擬器官方的教程填寫"
|
||||
},
|
||||
"PackageName": {
|
||||
"name": "遊戲伺服器",
|
||||
@ -108,7 +108,7 @@
|
||||
},
|
||||
"EmulatorInfo": {
|
||||
"_info": {
|
||||
"name": "模擬器設置",
|
||||
"name": "模擬器設定",
|
||||
"help": "下列數值是根據Serial自動填充的,如果不懂請不要隨意修改"
|
||||
},
|
||||
"Emulator": {
|
||||
@ -159,7 +159,7 @@
|
||||
},
|
||||
"OnePushConfig": {
|
||||
"name": "錯誤推送設定",
|
||||
"help": "發生無法處理的异常後,使用 Onepush 推送错误消息。設定參考文檔:https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Onepush-configuration-%5BCN%5D"
|
||||
"help": "發生無法處理的異常後,使用 Onepush 推送错误消息。設定參考文檔:https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/Onepush-configuration-%5BCN%5D"
|
||||
}
|
||||
},
|
||||
"Optimization": {
|
||||
@ -190,7 +190,7 @@
|
||||
},
|
||||
"Name": {
|
||||
"name": "副本名稱",
|
||||
"help": "默認打本設置",
|
||||
"help": "默認打本設定",
|
||||
"Calyx_Golden_Memories": "材料:角色經驗(回憶之蕾•擬造花萼金)",
|
||||
"Calyx_Golden_Aether": "材料:武器經驗(乙太之蕾•擬造花萼金)",
|
||||
"Calyx_Golden_Treasures": "材料:信用點(藏珍之蕾•擬造花萼金)",
|
||||
@ -221,7 +221,7 @@
|
||||
},
|
||||
"NameAtDoubleCalyx": {
|
||||
"name": "有雙倍花活動時,選擇副本",
|
||||
"help": "次數耗儘後回退到默認打本設置",
|
||||
"help": "次數耗儘後回退到默認打本設定",
|
||||
"do_not_participate": "不參與活動",
|
||||
"Calyx_Golden_Memories": "材料:角色經驗(回憶之蕾•擬造花萼金)",
|
||||
"Calyx_Golden_Aether": "材料:武器經驗(乙太之蕾•擬造花萼金)",
|
||||
@ -234,6 +234,18 @@
|
||||
"Calyx_Crimson_Harmony": "行跡材料:同諧(同諧之蕾•擬造花萼赤)",
|
||||
"Calyx_Crimson_Nihility": "行跡材料:虛無(虛無之蕾•擬造花萼赤)"
|
||||
},
|
||||
"NameAtDoubleRelic": {
|
||||
"name": "有遺器活動時,選擇副本",
|
||||
"help": "次數耗儘後回退到默認打本設定",
|
||||
"do_not_participate": "不參與活動",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "遺器:冰套+風套(霜風之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遺器:物理套+擊破套(迅拳之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "遺器:治療套+快槍手(漂泊之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "遺器:鐵衛套+量子套(睿治之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "遺器:防禦套+雷套(聖頌之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "遺器:火套+虛數套(野焰之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遺器:生命套+速度套(藥使之徑•侵蝕隧洞)"
|
||||
},
|
||||
"Team": {
|
||||
"name": "打本隊伍",
|
||||
"help": "",
|
||||
@ -243,18 +255,77 @@
|
||||
"4": "4",
|
||||
"5": "5",
|
||||
"6": "6"
|
||||
}
|
||||
},
|
||||
"DungeonDaily": {
|
||||
"_info": {
|
||||
"name": "每日任務設定",
|
||||
"help": "打一次特定的本,以滿足每日任務的要求"
|
||||
},
|
||||
"Support": {
|
||||
"name": "Dungeon.Support.name",
|
||||
"help": "Dungeon.Support.help",
|
||||
"do_not_use": "do_not_use",
|
||||
"always_use": "always_use",
|
||||
"when_daily": "when_daily"
|
||||
"CalyxGolden": {
|
||||
"name": "完成1次擬造花萼(金)",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成這個任務",
|
||||
"Calyx_Golden_Memories": "材料:角色經驗(回憶之蕾•擬造花萼金)",
|
||||
"Calyx_Golden_Aether": "材料:武器經驗(乙太之蕾•擬造花萼金)",
|
||||
"Calyx_Golden_Treasures": "材料:信用點(藏珍之蕾•擬造花萼金)"
|
||||
},
|
||||
"SupportCharacter": {
|
||||
"name": "Dungeon.SupportCharacter.name",
|
||||
"help": "Dungeon.SupportCharacter.help",
|
||||
"FirstCharacter": "FirstCharacter",
|
||||
"CalyxCrimson": {
|
||||
"name": "完成1次擬造花萼(赤)",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成這個任務",
|
||||
"Calyx_Crimson_Destruction": "行跡材料:毀滅(毀滅之蕾•擬造花萼赤)",
|
||||
"Calyx_Crimson_Preservation": "行跡材料:存護(存護之蕾•擬造花萼赤)",
|
||||
"Calyx_Crimson_Hunt": "行跡材料:巡獵(存護之蕾•擬造花萼赤)",
|
||||
"Calyx_Crimson_Abundance": "行跡材料:豐饒(豐饒之蕾•擬造花萼赤)",
|
||||
"Calyx_Crimson_Erudition": "行跡材料:智識(智識之蕾•擬造花萼赤)",
|
||||
"Calyx_Crimson_Harmony": "行跡材料:同諧(同諧之蕾•擬造花萼赤)",
|
||||
"Calyx_Crimson_Nihility": "行跡材料:虛無(虛無之蕾•擬造花萼赤)"
|
||||
},
|
||||
"StagnantShadow": {
|
||||
"name": "完成1次凝滯虛影",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成這個任務",
|
||||
"Stagnant_Shadow_Quanta": "角色晉階材料:量子(空海之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Gust": "角色晉階材料:風(巽風之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Fulmination": "角色晉階材料:雷(鳴雷之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Blaze": "角色晉階材料:火(炎華之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Spike": "角色晉階材料:物理(鋒芒之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Rime": "角色晉階材料:冰(霜晶之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Mirage": "角色晉階材料:虛數(幻光之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Icicle": "角色晉階材料:冰(冰稜之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Doom": "角色晉階材料:雷(震厄之形•凝滯虛影)",
|
||||
"Stagnant_Shadow_Celestial": "角色晉階材料:風(天人之形•凝滯虛影)"
|
||||
},
|
||||
"CavernOfCorrosion": {
|
||||
"name": "完成1次侵蝕隧洞",
|
||||
"help": "",
|
||||
"do_not_achieve": "不完成這個任務",
|
||||
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "遺器:冰套+風套(霜風之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遺器:物理套+擊破套(迅拳之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Drifting": "遺器:治療套+快槍手(漂泊之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Providence": "遺器:鐵衛套+量子套(睿治之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "遺器:防禦套+雷套(聖頌之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Conflagration": "遺器:火套+虛數套(野焰之徑•侵蝕隧洞)",
|
||||
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遺器:生命套+速度套(藥使之徑•侵蝕隧洞)"
|
||||
}
|
||||
},
|
||||
"DungeonSupport": {
|
||||
"_info": {
|
||||
"name": "支援設定",
|
||||
"help": ""
|
||||
},
|
||||
"Use": {
|
||||
"name": "使用好友支援",
|
||||
"help": "",
|
||||
"always_use": "總是使用",
|
||||
"when_daily": "僅當每日任務需要時使用",
|
||||
"do_not_use": "不使用"
|
||||
},
|
||||
"Character": {
|
||||
"name": "好友支援角色",
|
||||
"help": "選擇好友支援角色,未找到則選擇默認(第一個)角色",
|
||||
"FirstCharacter": "支援列表第一個角色",
|
||||
"Arlan": "阿蘭",
|
||||
"Asta": "艾絲妲",
|
||||
"Bailu": "白露",
|
||||
@ -267,6 +338,8 @@
|
||||
"Himeko": "姬子",
|
||||
"Hook": "虎克",
|
||||
"JingYuan": "景元",
|
||||
"Kafka": "卡芙卡",
|
||||
"Luka": "盧卡",
|
||||
"Luocha": "羅剎",
|
||||
"March7th": "三月七",
|
||||
"Natasha": "娜塔莎",
|
||||
@ -278,16 +351,221 @@
|
||||
"SilverWolf": "銀狼",
|
||||
"Sushang": "素裳",
|
||||
"Tingyun": "停雲",
|
||||
"TrailblazerDestruction": "TrailblazerDestruction",
|
||||
"TrailblazerPreservation": "TrailblazerPreservation",
|
||||
"TrailblazerDestruction": "開拓者•毀滅",
|
||||
"TrailblazerPreservation": "開拓者•存護",
|
||||
"Welt": "瓦爾特",
|
||||
"Yanqing": "彥卿",
|
||||
"Yukong": "馭空"
|
||||
}
|
||||
},
|
||||
"DungeonStorage": {
|
||||
"_info": {
|
||||
"name": "DungeonStorage._info.name",
|
||||
"help": "DungeonStorage._info.help"
|
||||
},
|
||||
"DungeonDouble": {
|
||||
"name": "DungeonStorage.DungeonDouble.name",
|
||||
"help": "DungeonStorage.DungeonDouble.help"
|
||||
}
|
||||
},
|
||||
"AchievableQuest": {
|
||||
"_info": {
|
||||
"name": "可完成的任務",
|
||||
"help": "任務狀態為 \"未設定\" 時需要按照要求設定SRC,才能啟用任務\n注意:請讓更多的任務處於 \"可完成\" 狀態,否則SRC可能無法完成500點的活躍度要求"
|
||||
},
|
||||
"Complete_1_Daily_Mission": {
|
||||
"name": "完成1個每日任務",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Clear_Calyx_Golden_1_times": {
|
||||
"name": "完成1次「擬造花萼(金)」",
|
||||
"help": "需要設定並啟用\"每日副本\"任務",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Complete_Calyx_Crimson_1_time": {
|
||||
"name": "完成1次「擬造花萼(赤)」",
|
||||
"help": "需要設定並啟用\"每日副本\"任務",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Clear_Stagnant_Shadow_1_times": {
|
||||
"name": "完成1次「凝滯虛影」",
|
||||
"help": "需要設定並啟用\"每日副本\"任務",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Clear_Cavern_of_Corrosion_1_times": {
|
||||
"name": "完成1次「侵蝕隧洞」",
|
||||
"help": "需要設定並啟用\"每日副本\"任務",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": {
|
||||
"name": "單場戰鬥中,觸發3種不同屬性的弱點擊破",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Inflict_Weakness_Break_5_times": {
|
||||
"name": "累積觸發弱點擊破效果5次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Defeat_a_total_of_20_enemies": {
|
||||
"name": "累積消滅20個敵人",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Enter_combat_by_attacking_enemy_Weakness_and_win_3_times": {
|
||||
"name": "利用弱點進入戰鬥並獲勝3次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Use_Technique_2_times": {
|
||||
"name": "累積施放2次秘技",
|
||||
"help": "默認可完成,將前往深淵一施放2次秘技",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Go_on_assignment_1_time": {
|
||||
"name": "派遣1次委託",
|
||||
"help": "需要設定並啟用\"委託\"任務",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Take_1_photo": {
|
||||
"name": "拍照1次",
|
||||
"help": "默认可完成",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Destroy_3_destructible_objects": {
|
||||
"name": "累積擊碎3個可破壞物",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Complete_Forgotten_Hall_1_time": {
|
||||
"name": "完成1次「忘卻之庭」",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Complete_Echo_of_War_1_times": {
|
||||
"name": "完成1次「歷戰餘響」",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Complete_1_stage_in_Simulated_Universe_Any_world": {
|
||||
"name": "完成「模擬宇宙」任意世界的1個區域",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Obtain_victory_in_combat_with_support_characters_1_time": {
|
||||
"name": "使用支援角色並獲得戰鬥勝利1次",
|
||||
"help": "需要設定並啟用\"每日副本\",且設並好友支援",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Use_an_Ultimate_to_deal_the_final_blow_1_time": {
|
||||
"name": "施放終結技造成制勝一擊1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Level_up_any_character_1_time": {
|
||||
"name": "將任意角色等級提升1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Level_up_any_Light_Cone_1_time": {
|
||||
"name": "將任意光錐等級提升1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Level_up_any_Relic_1_time": {
|
||||
"name": "將任意遺器等級提升1次",
|
||||
"help": "",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Salvage_any_Relic": {
|
||||
"name": "分解任意1件遺器",
|
||||
"help": "默認可完成,將分解遺器稀有度倒序的第一個",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Synthesize_Consumable_1_time": {
|
||||
"name": "合成1次消耗品",
|
||||
"help": "默認可完成,將合成最低級零食",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Synthesize_material_1_time": {
|
||||
"name": "合成1次素材",
|
||||
"help": "默認可完成,將合成最低級素材",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
},
|
||||
"Use_Consumables_1_time": {
|
||||
"name": "使用1件消耗品",
|
||||
"help": "默認可完成,將使用護具,無材料時先合成再使用",
|
||||
"achievable": "可完成",
|
||||
"not_set": "未設定",
|
||||
"not_supported": "暫未支援"
|
||||
}
|
||||
},
|
||||
"DailyStorage": {
|
||||
"_info": {
|
||||
"name": "DailyStorage._info.name",
|
||||
"help": "DailyStorage._info.help"
|
||||
},
|
||||
"DailyActivity": {
|
||||
"name": "DailyStorage.DailyActivity.name",
|
||||
"help": "DailyStorage.DailyActivity.help"
|
||||
},
|
||||
"DailyQuest": {
|
||||
"name": "DailyStorage.DailyQuest.name",
|
||||
"help": "DailyStorage.DailyQuest.help"
|
||||
}
|
||||
},
|
||||
"Assignment": {
|
||||
"_info": {
|
||||
"name": "委託設置",
|
||||
"name": "委託設定",
|
||||
"help": "領取獎勵並派遣,優先處理指定委託\n若處理指定委託之後未達到上限,則按經驗材料 → 角色專屬素材 → 合成材料的順序來派遣委託"
|
||||
},
|
||||
"Duration": {
|
||||
@ -509,17 +787,17 @@
|
||||
"UpdateStart": "開始更新",
|
||||
"UpdateWait": "等待所有 Alas 完成當前任務",
|
||||
"UpdateRun": "更新中",
|
||||
"UpdateSuccess": "更新成功,正在重啓",
|
||||
"UpdateSuccess": "更新成功,正在重啟",
|
||||
"UpdateFailed": "更新失敗,可在./log/*_gui.txt中找到錯誤日誌",
|
||||
"UpdateChecking": "檢查更新中",
|
||||
"UpdateCancel": "取消更新,重啓 Alas 中",
|
||||
"UpdateFinish": "更新成功,請手動重啓",
|
||||
"UpdateCancel": "取消更新,重啟 Alas 中",
|
||||
"UpdateFinish": "更新成功,請手動重啟",
|
||||
"Local": "本地",
|
||||
"Upstream": "上游倉庫",
|
||||
"Author": "作者",
|
||||
"Time": "提交時間",
|
||||
"Message": "提交資訊",
|
||||
"DisabledWarn": "更新模塊未啟用,你需要手動重啓 Alas 進行更新",
|
||||
"DisabledWarn": "更新模塊未啟用,你需要手動重啟 Alas 進行更新",
|
||||
"DetailedHistory": "詳細提交歷史"
|
||||
},
|
||||
"Remote": {
|
||||
|
242
module/config/stored/classes.py
Normal file
242
module/config/stored/classes.py
Normal file
@ -0,0 +1,242 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
from functools import cached_property as functools_cached_property
|
||||
|
||||
from module.base.decorator import cached_property
|
||||
from module.config.utils import DEFAULT_TIME, deep_get, get_server_last_update
|
||||
from module.exception import ScriptError
|
||||
|
||||
|
||||
def now():
|
||||
return datetime.now().replace(microsecond=0)
|
||||
|
||||
|
||||
def iter_attribute(cls):
|
||||
"""
|
||||
Args:
|
||||
cls: Class or object
|
||||
|
||||
Yields:
|
||||
str, obj: Attribute name, attribute value
|
||||
"""
|
||||
for attr in dir(cls):
|
||||
if attr.startswith('_'):
|
||||
continue
|
||||
value = getattr(cls, attr)
|
||||
if type(value).__name__ in ['function', 'property']:
|
||||
continue
|
||||
yield attr, value
|
||||
|
||||
|
||||
class StoredBase:
|
||||
time = DEFAULT_TIME
|
||||
|
||||
def __init__(self, key):
|
||||
self._key = key
|
||||
self._config = None
|
||||
|
||||
@cached_property
|
||||
def _name(self):
|
||||
return self._key.split('.')[-1]
|
||||
|
||||
def _bind(self, config):
|
||||
"""
|
||||
Args:
|
||||
config (AzurLaneConfig):
|
||||
"""
|
||||
self._config = config
|
||||
|
||||
@functools_cached_property
|
||||
def _stored(self):
|
||||
assert self._config is not None, 'StoredBase._bind() must be called before getting stored data'
|
||||
from module.logger import logger
|
||||
|
||||
out = {}
|
||||
stored = deep_get(self._config.data, keys=self._key, default={})
|
||||
for attr, default in self._attrs.items():
|
||||
value = stored.get(attr, default)
|
||||
if attr == 'time':
|
||||
if not isinstance(value, datetime):
|
||||
try:
|
||||
value = datetime.fromisoformat(value)
|
||||
except ValueError:
|
||||
logger.warning(f'{self._name} has invalid attr: {attr}={value}, use default={default}')
|
||||
value = default
|
||||
else:
|
||||
if not isinstance(value, type(default)):
|
||||
logger.warning(f'{self._name} has invalid attr: {attr}={value}, use default={default}')
|
||||
value = default
|
||||
|
||||
out[attr] = value
|
||||
return out
|
||||
|
||||
@cached_property
|
||||
def _attrs(self) -> dict:
|
||||
"""
|
||||
All attributes defined
|
||||
"""
|
||||
attrs = {
|
||||
# time is the first one
|
||||
'time': DEFAULT_TIME
|
||||
}
|
||||
for attr, value in iter_attribute(self.__class__):
|
||||
attrs[attr] = value
|
||||
return attrs
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in self._attrs:
|
||||
stored = self._stored
|
||||
stored['time'] = now()
|
||||
stored[key] = value
|
||||
self._config.modified[self._key] = stored
|
||||
if self._config.auto_update:
|
||||
self._config.update()
|
||||
else:
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def __getattribute__(self, item):
|
||||
if not item.startswith('_') and item in self._attrs:
|
||||
return self._stored[item]
|
||||
else:
|
||||
return super().__getattribute__(item)
|
||||
|
||||
def is_expired(self) -> bool:
|
||||
return False
|
||||
|
||||
def show(self):
|
||||
"""
|
||||
Log self
|
||||
"""
|
||||
from module.logger import logger
|
||||
logger.attr(self._name, self._stored)
|
||||
|
||||
def dashboard(self) -> str:
|
||||
"""
|
||||
Return a string to show on GUI
|
||||
"""
|
||||
return 'None'
|
||||
|
||||
def readable_time(self):
|
||||
diff = self.time.timestamp() - time.time()
|
||||
if diff < -1:
|
||||
return '', 'TimeError'
|
||||
elif diff < 60:
|
||||
# < 1 min
|
||||
return '', 'JustNow'
|
||||
elif diff < 3600:
|
||||
return str(int(diff // 60)), 'MinutesAgo'
|
||||
elif diff < 86400:
|
||||
return str(int(diff // 86400)), 'HoursAgo'
|
||||
elif diff < 129600:
|
||||
return str(int(diff // 129600)), 'DaysAgo'
|
||||
else:
|
||||
# > 15 days
|
||||
return '', 'LongTimeAgo'
|
||||
|
||||
|
||||
class StoredExpiredAt0400(StoredBase):
|
||||
def is_expired(self):
|
||||
from module.logger import logger
|
||||
self.show()
|
||||
expired = self.time < get_server_last_update('04:00')
|
||||
logger.attr(f'{self._name} expired', expired)
|
||||
return expired
|
||||
|
||||
|
||||
class StoredInt(StoredBase):
|
||||
value = 0
|
||||
|
||||
|
||||
class StoredCounter(StoredBase):
|
||||
current = 0
|
||||
total = 0
|
||||
|
||||
def set(self, current, total):
|
||||
with self._config.multi_set():
|
||||
self.current = current
|
||||
self.total = total
|
||||
|
||||
def to_counter(self) -> str:
|
||||
return f'{self.current}/{self.total}'
|
||||
|
||||
def is_full(self) -> bool:
|
||||
return self.current >= self.total
|
||||
|
||||
def get_remain(self) -> int:
|
||||
return self.total - self.current
|
||||
|
||||
|
||||
class StoredDailyActivity(StoredCounter, StoredExpiredAt0400):
|
||||
def set(self, current):
|
||||
return super().set(current=current, total=500)
|
||||
|
||||
@property
|
||||
def _stored(self):
|
||||
stored = super()._stored
|
||||
stored['total'] = 500
|
||||
return stored
|
||||
|
||||
|
||||
class StoredDaily(StoredExpiredAt0400):
|
||||
quest1 = ''
|
||||
quest2 = ''
|
||||
quest3 = ''
|
||||
quest4 = ''
|
||||
quest5 = ''
|
||||
quest6 = ''
|
||||
|
||||
def load_quests(self):
|
||||
"""
|
||||
Returns:
|
||||
list[DailyQuest]: Note that must check if quests are expired
|
||||
"""
|
||||
# DailyQuest should be lazy loaded
|
||||
from tasks.daily.keywords import DailyQuest
|
||||
quests = []
|
||||
for name in [self.quest1, self.quest2, self.quest3, self.quest4, self.quest5, self.quest6]:
|
||||
if not name:
|
||||
continue
|
||||
try:
|
||||
quest = DailyQuest.find(name)
|
||||
quests.append(quest)
|
||||
except ScriptError:
|
||||
pass
|
||||
return quests
|
||||
|
||||
def write_quests(self, quests):
|
||||
"""
|
||||
Args:
|
||||
quests (list[DailyQuest, str]):
|
||||
"""
|
||||
from tasks.daily.keywords import DailyQuest
|
||||
quests = [q.name if isinstance(q, DailyQuest) else q for q in quests]
|
||||
with self._config.multi_set():
|
||||
try:
|
||||
self.quest1 = quests[0]
|
||||
except IndexError:
|
||||
self.quest1 = ''
|
||||
try:
|
||||
self.quest2 = quests[1]
|
||||
except IndexError:
|
||||
self.quest2 = ''
|
||||
try:
|
||||
self.quest3 = quests[2]
|
||||
except IndexError:
|
||||
self.quest3 = ''
|
||||
try:
|
||||
self.quest4 = quests[3]
|
||||
except IndexError:
|
||||
self.quest4 = ''
|
||||
try:
|
||||
self.quest5 = quests[4]
|
||||
except IndexError:
|
||||
self.quest5 = ''
|
||||
try:
|
||||
self.quest6 = quests[5]
|
||||
except IndexError:
|
||||
self.quest6 = ''
|
||||
|
||||
|
||||
class StoredDungeonDouble(StoredExpiredAt0400):
|
||||
calyx = 0
|
||||
relic = 0
|
18
module/config/stored/stored_generated.py
Normal file
18
module/config/stored/stored_generated.py
Normal file
@ -0,0 +1,18 @@
|
||||
from module.config.stored.classes import (
|
||||
StoredBase,
|
||||
StoredCounter,
|
||||
StoredDaily,
|
||||
StoredDailyActivity,
|
||||
StoredDungeonDouble,
|
||||
StoredExpiredAt0400,
|
||||
StoredInt,
|
||||
)
|
||||
|
||||
|
||||
# This file was auto-generated, do not modify it manually. To generate:
|
||||
# ``` python -m module/config/config_updater.py ```
|
||||
|
||||
class StoredGenerated:
|
||||
DungeonDouble = StoredDungeonDouble("Dungeon.DungeonStorage.DungeonDouble")
|
||||
DailyActivity = StoredDailyActivity("DailyQuest.DailyStorage.DailyActivity")
|
||||
DailyQuest = StoredDaily("DailyQuest.DailyStorage.DailyQuest")
|
@ -332,8 +332,9 @@ def data_to_type(data, **kwargs):
|
||||
"""
|
||||
| Condition | Type |
|
||||
| ------------------------------------ | -------- |
|
||||
| Value is bool | checkbox |
|
||||
| Arg has options | select |
|
||||
| `value` is bool | checkbox |
|
||||
| Arg has `options` | select |
|
||||
| Arg has `stored` | select |
|
||||
| `Filter` is in name (in data['arg']) | textarea |
|
||||
| Rest of the args | input |
|
||||
|
||||
@ -345,10 +346,12 @@ def data_to_type(data, **kwargs):
|
||||
str:
|
||||
"""
|
||||
kwargs.update(data)
|
||||
if isinstance(kwargs['value'], bool):
|
||||
if isinstance(kwargs.get('value'), bool):
|
||||
return 'checkbox'
|
||||
elif 'option' in kwargs and kwargs['option']:
|
||||
return 'select'
|
||||
elif 'stored' in kwargs and kwargs['stored']:
|
||||
return 'stored'
|
||||
elif 'Filter' in kwargs['arg']:
|
||||
return 'textarea'
|
||||
else:
|
||||
|
@ -111,6 +111,8 @@ class Connection(ConnectionAttr):
|
||||
logger.attr('PackageName', self.package)
|
||||
logger.attr('Server', self.config.SERVER)
|
||||
|
||||
self.check_mumu_app_keep_alive()
|
||||
|
||||
@Config.when(DEVICE_OVER_HTTP=False)
|
||||
def adb_command(self, cmd, timeout=10):
|
||||
"""
|
||||
@ -216,13 +218,25 @@ class Connection(ConnectionAttr):
|
||||
# str
|
||||
return result
|
||||
|
||||
def adb_getprop(self, name):
|
||||
"""
|
||||
Get system property in Android, same as `getprop <name>`
|
||||
|
||||
Args:
|
||||
name (str): Property name
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"""
|
||||
return self.adb_shell(['getprop', name]).strip()
|
||||
|
||||
@cached_property
|
||||
def cpu_abi(self) -> str:
|
||||
"""
|
||||
Returns:
|
||||
str: arm64-v8a, armeabi-v7a, x86, x86_64
|
||||
"""
|
||||
abi = self.adb_shell(['getprop', 'ro.product.cpu.abi']).strip()
|
||||
abi = self.adb_getprop('ro.product.cpu.abi')
|
||||
if not len(abi):
|
||||
logger.error(f'CPU ABI invalid: "{abi}"')
|
||||
return abi
|
||||
@ -232,7 +246,7 @@ class Connection(ConnectionAttr):
|
||||
"""
|
||||
Android SDK/API levels, see https://apilevels.com/
|
||||
"""
|
||||
sdk = self.adb_shell(['getprop', 'ro.build.version.sdk']).strip()
|
||||
sdk = self.adb_getprop('ro.build.version.sdk')
|
||||
try:
|
||||
return int(sdk)
|
||||
except ValueError:
|
||||
@ -244,12 +258,32 @@ class Connection(ConnectionAttr):
|
||||
def is_avd(self):
|
||||
if get_serial_pair(self.serial)[0] is None:
|
||||
return False
|
||||
if 'ranchu' in self.adb_shell(['getprop', 'ro.hardware']):
|
||||
if 'ranchu' in self.adb_getprop('ro.hardware'):
|
||||
return True
|
||||
if 'goldfish' in self.adb_shell(['getprop', 'ro.hardware.audio.primary']):
|
||||
if 'goldfish' in self.adb_getprop('ro.hardware.audio.primary'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_mumu_app_keep_alive(self):
|
||||
if not self.is_mumu_family:
|
||||
return False
|
||||
|
||||
res = self.adb_getprop('nemud.app_keep_alive')
|
||||
logger.attr('nemud.app_keep_alive', res)
|
||||
if res == '':
|
||||
# Empry property, might not be a mumu emulator or might be an old mumu
|
||||
return True
|
||||
elif res == 'false':
|
||||
# Disabled
|
||||
return True
|
||||
elif res == 'true':
|
||||
# https://mumu.163.com/help/20230802/35047_1102450.html
|
||||
logger.critical('请在MuMu模拟器设置内关闭 "后台挂机时保活运行"')
|
||||
raise RequestHumanTakeover
|
||||
else:
|
||||
logger.warning(f'Invalid nemud.app_keep_alive value: {res}')
|
||||
return False
|
||||
|
||||
@cached_property
|
||||
def _nc_server_host_port(self):
|
||||
"""
|
||||
|
@ -101,7 +101,9 @@ class ConnectionAttr:
|
||||
|
||||
@cached_property
|
||||
def is_mumu_family(self):
|
||||
return self.serial == '127.0.0.1:7555'
|
||||
# 127.0.0.1:7555
|
||||
# 127.0.0.1:16384 + 32*n
|
||||
return self.serial == '127.0.0.1:7555' or self.serial.startswith('127.0.0.1:16')
|
||||
|
||||
@cached_property
|
||||
def is_emulator(self):
|
||||
|
@ -77,6 +77,19 @@ def retry(func):
|
||||
return retry_wrapper
|
||||
|
||||
|
||||
class MaatouchBuilder(CommandBuilder):
|
||||
def __init__(self, device, contact=0, handle_orientation=False):
|
||||
"""
|
||||
Args:
|
||||
device (MaaTouch):
|
||||
"""
|
||||
|
||||
super().__init__(device, contact, handle_orientation)
|
||||
|
||||
def send(self):
|
||||
return self.device.maatouch_send(builder=self)
|
||||
|
||||
|
||||
class MaaTouchNotInstalledError(Exception):
|
||||
pass
|
||||
|
||||
@ -94,7 +107,7 @@ class MaaTouch(Connection):
|
||||
@cached_property
|
||||
def maatouch_builder(self):
|
||||
self.maatouch_init()
|
||||
return CommandBuilder(self, handle_orientation=False)
|
||||
return MaatouchBuilder(self)
|
||||
|
||||
def maatouch_init(self):
|
||||
logger.hr('MaaTouch init')
|
||||
@ -165,14 +178,14 @@ class MaaTouch(Connection):
|
||||
)
|
||||
)
|
||||
|
||||
def maatouch_send(self):
|
||||
content = self.maatouch_builder.to_minitouch()
|
||||
def maatouch_send(self, builder: MaatouchBuilder):
|
||||
content = builder.to_minitouch()
|
||||
# logger.info("send operation: {}".format(content.replace("\n", "\\n")))
|
||||
byte_content = content.encode('utf-8')
|
||||
self._maatouch_stream.sendall(byte_content)
|
||||
self._maatouch_stream.recv(0)
|
||||
self.sleep(self.maatouch_builder.delay / 1000 + self.maatouch_builder.DEFAULT_DELAY)
|
||||
self.maatouch_builder.clear()
|
||||
self.sleep(self.maatouch_builder.delay / 1000 + builder.DEFAULT_DELAY)
|
||||
builder.clear()
|
||||
|
||||
def maatouch_install(self):
|
||||
logger.hr('MaaTouch install')
|
||||
@ -187,7 +200,7 @@ class MaaTouch(Connection):
|
||||
builder = self.maatouch_builder
|
||||
builder.down(x, y).commit()
|
||||
builder.up().commit()
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
@retry
|
||||
def long_click_maatouch(self, x, y, duration=1.0):
|
||||
@ -195,7 +208,7 @@ class MaaTouch(Connection):
|
||||
builder = self.maatouch_builder
|
||||
builder.down(x, y).commit().wait(duration)
|
||||
builder.up().commit()
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
@retry
|
||||
def swipe_maatouch(self, p1, p2):
|
||||
@ -203,14 +216,14 @@ class MaaTouch(Connection):
|
||||
builder = self.maatouch_builder
|
||||
|
||||
builder.down(*points[0]).commit()
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
for point in points[1:]:
|
||||
builder.move(*point).commit().wait(10)
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
builder.up().commit()
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
@retry
|
||||
def drag_maatouch(self, p1, p2, point_random=(-10, -10, 10, 10)):
|
||||
@ -220,15 +233,15 @@ class MaaTouch(Connection):
|
||||
builder = self.maatouch_builder
|
||||
|
||||
builder.down(*points[0]).commit()
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
for point in points[1:]:
|
||||
builder.move(*point).commit().wait(10)
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
builder.move(*p2).commit().wait(140)
|
||||
builder.move(*p2).commit().wait(140)
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
||||
builder.up().commit()
|
||||
self.maatouch_send()
|
||||
builder.send()
|
||||
|
@ -184,7 +184,7 @@ class CommandBuilder:
|
||||
max_x = 1280
|
||||
max_y = 720
|
||||
|
||||
def __init__(self, device, handle_orientation=True):
|
||||
def __init__(self, device, contact=0, handle_orientation=True):
|
||||
"""
|
||||
Args:
|
||||
device:
|
||||
@ -192,6 +192,7 @@ class CommandBuilder:
|
||||
self.device = device
|
||||
self.commands = []
|
||||
self.delay = 0
|
||||
self.contact = contact
|
||||
self.handle_orientation = handle_orientation
|
||||
|
||||
@property
|
||||
@ -243,21 +244,21 @@ class CommandBuilder:
|
||||
self.delay += ms
|
||||
return self
|
||||
|
||||
def up(self, contact=0):
|
||||
def up(self):
|
||||
""" add minitouch command: 'u <contact>\n' """
|
||||
self.commands.append(Command('u', contact=contact))
|
||||
self.commands.append(Command('u', contact=self.contact))
|
||||
return self
|
||||
|
||||
def down(self, x, y, contact=0, pressure=100):
|
||||
def down(self, x, y, pressure=100):
|
||||
""" add minitouch command: 'd <contact> <x> <y> <pressure>\n' """
|
||||
x, y = self.convert(x, y)
|
||||
self.commands.append(Command('d', x=x, y=y, contact=contact, pressure=pressure))
|
||||
self.commands.append(Command('d', x=x, y=y, contact=self.contact, pressure=pressure))
|
||||
return self
|
||||
|
||||
def move(self, x, y, contact=0, pressure=100):
|
||||
def move(self, x, y, pressure=100):
|
||||
""" add minitouch command: 'm <contact> <x> <y> <pressure>\n' """
|
||||
x, y = self.convert(x, y)
|
||||
self.commands.append(Command('m', x=x, y=y, contact=contact, pressure=pressure))
|
||||
self.commands.append(Command('m', x=x, y=y, contact=self.contact, pressure=pressure))
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
@ -271,6 +272,9 @@ class CommandBuilder:
|
||||
def to_atx_agent(self) -> List[str]:
|
||||
return [command.to_atx_agent(self.max_x, self.max_y) for command in self.commands]
|
||||
|
||||
def send(self):
|
||||
return self.device.minitouch_send(builder=self)
|
||||
|
||||
|
||||
class MinitouchNotInstalledError(Exception):
|
||||
pass
|
||||
@ -446,14 +450,14 @@ class Minitouch(Connection):
|
||||
)
|
||||
|
||||
@Config.when(DEVICE_OVER_HTTP=False)
|
||||
def minitouch_send(self):
|
||||
content = self.minitouch_builder.to_minitouch()
|
||||
def minitouch_send(self, builder: CommandBuilder):
|
||||
content = builder.to_minitouch()
|
||||
# logger.info("send operation: {}".format(content.replace("\n", "\\n")))
|
||||
byte_content = content.encode('utf-8')
|
||||
self._minitouch_client.sendall(byte_content)
|
||||
self._minitouch_client.recv(0)
|
||||
time.sleep(self.minitouch_builder.delay / 1000 + self.minitouch_builder.DEFAULT_DELAY)
|
||||
self.minitouch_builder.clear()
|
||||
time.sleep(self.minitouch_builder.delay / 1000 + builder.DEFAULT_DELAY)
|
||||
builder.clear()
|
||||
|
||||
@cached_property
|
||||
def _minitouch_loop(self):
|
||||
@ -514,8 +518,8 @@ class Minitouch(Connection):
|
||||
self._minitouch_ws = self._minitouch_loop_run(connect())
|
||||
|
||||
@Config.when(DEVICE_OVER_HTTP=True)
|
||||
def minitouch_send(self):
|
||||
content = self.minitouch_builder.to_atx_agent()
|
||||
def minitouch_send(self, builder: CommandBuilder):
|
||||
content = builder.to_atx_agent()
|
||||
|
||||
async def send():
|
||||
for row in content:
|
||||
@ -523,15 +527,15 @@ class Minitouch(Connection):
|
||||
await self._minitouch_ws.send(row)
|
||||
|
||||
self._minitouch_loop_run(send())
|
||||
time.sleep(self.minitouch_builder.delay / 1000 + self.minitouch_builder.DEFAULT_DELAY)
|
||||
self.minitouch_builder.clear()
|
||||
time.sleep(builder.delay / 1000 + builder.DEFAULT_DELAY)
|
||||
builder.clear()
|
||||
|
||||
@retry
|
||||
def click_minitouch(self, x, y):
|
||||
builder = self.minitouch_builder
|
||||
builder.down(x, y).commit()
|
||||
builder.up().commit()
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
@retry
|
||||
def long_click_minitouch(self, x, y, duration=1.0):
|
||||
@ -539,7 +543,7 @@ class Minitouch(Connection):
|
||||
builder = self.minitouch_builder
|
||||
builder.down(x, y).commit().wait(duration)
|
||||
builder.up().commit()
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
@retry
|
||||
def swipe_minitouch(self, p1, p2):
|
||||
@ -547,14 +551,14 @@ class Minitouch(Connection):
|
||||
builder = self.minitouch_builder
|
||||
|
||||
builder.down(*points[0]).commit()
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
for point in points[1:]:
|
||||
builder.move(*point).commit().wait(10)
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
builder.up().commit()
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
@retry
|
||||
def drag_minitouch(self, p1, p2, point_random=(-10, -10, 10, 10)):
|
||||
@ -564,15 +568,15 @@ class Minitouch(Connection):
|
||||
builder = self.minitouch_builder
|
||||
|
||||
builder.down(*points[0]).commit()
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
for point in points[1:]:
|
||||
builder.move(*point).commit().wait(10)
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
builder.move(*p2).commit().wait(140)
|
||||
builder.move(*p2).commit().wait(140)
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
||||
builder.up().commit()
|
||||
self.minitouch_send()
|
||||
builder.send()
|
||||
|
@ -1,3 +1,4 @@
|
||||
import copy
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
@ -324,6 +325,35 @@ def put_arg_input(kwargs: T_Output_Kwargs) -> Output:
|
||||
)
|
||||
|
||||
|
||||
def product_stored_row(kwargs: T_Output_Kwargs, key, value):
|
||||
kwargs = copy.copy(kwargs)
|
||||
kwargs["name"] += f'_{key}'
|
||||
kwargs["value"] = value
|
||||
return put_input(**kwargs).style("--input--")
|
||||
|
||||
|
||||
def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
|
||||
name: str = kwargs["name"]
|
||||
kwargs["disabled"] = True
|
||||
|
||||
values = kwargs.pop("value", {})
|
||||
time_ = values.pop("time", "")
|
||||
|
||||
rows = [product_stored_row(kwargs, key, value) for key, value in values.items() if value]
|
||||
if time_:
|
||||
rows += [product_stored_row(kwargs, "time", time_)]
|
||||
return put_scope(
|
||||
f"arg_container-stored-{name}",
|
||||
[
|
||||
get_title_help(kwargs),
|
||||
put_scope(
|
||||
f"arg_stored-stored-value-{name}",
|
||||
rows,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def put_arg_select(kwargs: T_Output_Kwargs) -> Output:
|
||||
name: str = kwargs["name"]
|
||||
value: str = kwargs["value"]
|
||||
@ -355,6 +385,37 @@ def put_arg_select(kwargs: T_Output_Kwargs) -> Output:
|
||||
)
|
||||
|
||||
|
||||
def put_arg_state(kwargs: T_Output_Kwargs) -> Output:
|
||||
name: str = kwargs["name"]
|
||||
value: str = kwargs["value"]
|
||||
options: List[str] = kwargs["options"]
|
||||
options_label: List[str] = kwargs.pop("options_label", [])
|
||||
_: str = kwargs.pop("invalid_feedback", None)
|
||||
bold: bool = value in kwargs.pop("option_bold", [])
|
||||
light: bool = value in kwargs.pop("option_light", [])
|
||||
|
||||
option = [{
|
||||
"label": next((opt_label for opt, opt_label in zip(options, options_label) if opt == value), value),
|
||||
"value": value,
|
||||
"selected": True,
|
||||
}]
|
||||
if bold:
|
||||
kwargs["class"] = "form-control state state-bold"
|
||||
elif light:
|
||||
kwargs["class"] = "form-control state state-light"
|
||||
else:
|
||||
kwargs["class"] = "form-control state"
|
||||
kwargs["options"] = option
|
||||
|
||||
return put_scope(
|
||||
f"arg_container-select-{name}",
|
||||
[
|
||||
get_title_help(kwargs),
|
||||
put_select(**kwargs).style("--input--"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def put_arg_textarea(kwargs: T_Output_Kwargs) -> Output:
|
||||
name: str = kwargs["name"]
|
||||
mode: str = kwargs.pop("mode", None)
|
||||
@ -437,6 +498,8 @@ _widget_type_to_func: Dict[str, Callable] = {
|
||||
"textarea": put_arg_textarea,
|
||||
"checkbox": put_arg_checkbox,
|
||||
"storage": put_arg_storage,
|
||||
"state": put_arg_state,
|
||||
"stored": put_arg_stored,
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,19 +3,27 @@ from datetime import datetime
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import Duration
|
||||
from tasks.assignment.assets.assets_assignment_claim import CLAIM
|
||||
from tasks.assignment.assets.assets_assignment_ui import (DISPATCHED,
|
||||
OCR_ASSIGNMENT_TIME)
|
||||
from tasks.assignment.assets.assets_assignment_ui import (
|
||||
DISPATCHED,
|
||||
OCR_ASSIGNMENT_TIME,
|
||||
)
|
||||
from tasks.assignment.claim import AssignmentClaim
|
||||
from tasks.assignment.keywords import *
|
||||
from tasks.assignment.keywords import (
|
||||
AssignmentEntry,
|
||||
KEYWORDS_ASSIGNMENT_GROUP,
|
||||
)
|
||||
from tasks.base.page import page_assignment, page_menu
|
||||
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
|
||||
from tasks.daily.synthesize import SynthesizeUI
|
||||
|
||||
|
||||
class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
def run(self, assignments: list[AssignmentEntry] = None, duration: int = None):
|
||||
self.config.update_daily_quests()
|
||||
|
||||
if assignments is None:
|
||||
assignments = (
|
||||
getattr(self.config, f'Assignment_Name_{i+1}', None) for i in range(4))
|
||||
getattr(self.config, f'Assignment_Name_{i + 1}', None) for i in range(4))
|
||||
# remove duplicate while keeping order
|
||||
assignments = list(dict.fromkeys(
|
||||
x for x in assignments if x is not None))
|
||||
@ -46,7 +54,12 @@ class Assignment(AssignmentClaim, SynthesizeUI):
|
||||
# Scheduler
|
||||
delay = min(self.dispatched.values())
|
||||
logger.info(f'Delay assignment check to {str(delay)}')
|
||||
self.config.task_delay(target=delay)
|
||||
with self.config.multi_set():
|
||||
quests = self.config.stored.DailyQuest.load_quests()
|
||||
if KEYWORDS_DAILY_QUEST.Go_on_assignment_1_time in quests:
|
||||
logger.info('Achieved daily quest Go_on_assignment_1_time')
|
||||
self.config.task_call('DailyQuest')
|
||||
self.config.task_delay(target=delay)
|
||||
|
||||
def _check_inlist(self, assignments: list[AssignmentEntry], duration: int):
|
||||
"""
|
||||
|
@ -1,6 +1,6 @@
|
||||
import tasks.assignment.keywords.entry as KEYWORDS_ASSIGNMENT_ENTRY
|
||||
import tasks.assignment.keywords.group as KEYWORDS_ASSIGNMENT_GROUP
|
||||
from tasks.assignment.keywords.classes import AssignmentGroup, AssignmentEntry
|
||||
from tasks.assignment.keywords.classes import AssignmentEntry, AssignmentGroup
|
||||
|
||||
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials.entries = (
|
||||
KEYWORDS_ASSIGNMENT_ENTRY.Nine_Billion_Names,
|
||||
@ -22,9 +22,9 @@ KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials.entries = (
|
||||
KEYWORDS_ASSIGNMENT_ENTRY.The_Blossom_in_the_Storm,
|
||||
)
|
||||
for group in (
|
||||
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials,
|
||||
KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits,
|
||||
KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials,
|
||||
KEYWORDS_ASSIGNMENT_GROUP.Character_Materials,
|
||||
KEYWORDS_ASSIGNMENT_GROUP.EXP_Materials_Credits,
|
||||
KEYWORDS_ASSIGNMENT_GROUP.Synthesis_Materials,
|
||||
):
|
||||
for entry in group.entries:
|
||||
entry.group = group
|
||||
entry.group = group
|
||||
|
@ -8,6 +8,7 @@ from tasks.base.assets.assets_base_page import CLOSE
|
||||
from tasks.base.page import Page, page_main
|
||||
from tasks.base.popup import PopupHandler
|
||||
from tasks.base.state import StateMixin
|
||||
from tasks.combat.assets.assets_combat_finish import COMBAT_EXIT
|
||||
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
|
||||
|
||||
|
||||
@ -282,6 +283,8 @@ class UI(PopupHandler, StateMixin):
|
||||
if self.appear(COMBAT_PREPARE, interval=5):
|
||||
logger.info(f'UI additional: {COMBAT_PREPARE} -> {CLOSE}')
|
||||
self.device.click(CLOSE)
|
||||
if self.appear_then_click(COMBAT_EXIT, interval=5):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
@ -3,13 +3,12 @@ from module.logger import logger
|
||||
from tasks.base.assets.assets_base_page import CLOSE
|
||||
from tasks.combat.assets.assets_combat_finish import COMBAT_AGAIN, COMBAT_EXIT
|
||||
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
|
||||
from tasks.combat.assets.assets_combat_team import COMBAT_TEAM_PREPARE, COMBAT_TEAM_SUPPORT, COMBAT_TEAM_DISMISSSUPPORT
|
||||
from tasks.combat.assets.assets_combat_support import COMBAT_SUPPORT_ADD, COMBAT_SUPPORT_LIST
|
||||
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.state import CombatState
|
||||
from tasks.combat.team import CombatTeam
|
||||
from tasks.combat.support import CombatSupport
|
||||
from tasks.combat.team import CombatTeam
|
||||
from tasks.map.control.joystick import MapControlJoystick
|
||||
|
||||
|
||||
@ -69,9 +68,8 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
|
||||
"""
|
||||
Args:
|
||||
team: 1 to 6.
|
||||
skip_first_screenshot:
|
||||
support_character: Support character name
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if success to enter combat
|
||||
False if trialblaze power is not enough
|
||||
@ -272,21 +270,23 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
|
||||
self.device.click(COMBAT_EXIT)
|
||||
continue
|
||||
|
||||
def combat(self, team: int = 1, wave_limit: int = 0, skip_first_screenshot=True, support_character: str = None):
|
||||
def is_stamina_exhausted(self) -> bool:
|
||||
flag = self.state.TrailblazePower < self.combat_wave_cost
|
||||
logger.attr('StaminaExhausted', flag)
|
||||
return flag
|
||||
|
||||
def combat(self, team: int = 1, wave_limit: int = 0, support_character: str = None, skip_first_screenshot=True):
|
||||
"""
|
||||
Combat until trailblaze power runs out.
|
||||
|
||||
Args:
|
||||
team: 1 to 6.
|
||||
wave_limit: Limit combat runs, 0 means no limit.
|
||||
skip_first_screenshot:
|
||||
use_support: "do_not_use", "always_use", "when_daily"
|
||||
is_daily: True if is a daily task
|
||||
support_character: Support character name
|
||||
skip_first_screenshot:
|
||||
|
||||
Returns:
|
||||
bool: True if trailblaze power exhausted
|
||||
False if reached wave_limit but still have trailblaze power
|
||||
int: Run count
|
||||
|
||||
Pages:
|
||||
in: COMBAT_PREPARE
|
||||
@ -298,6 +298,7 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
|
||||
|
||||
self.combat_wave_limit = wave_limit
|
||||
self.combat_wave_done = 0
|
||||
run_count = 0
|
||||
while 1:
|
||||
logger.hr('Combat', level=2)
|
||||
logger.info(f'Combat, team={team}, wave={self.combat_wave_done}/{self.combat_wave_limit}')
|
||||
@ -312,7 +313,9 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
|
||||
finish = self.combat_finish()
|
||||
if self._combat_should_reenter():
|
||||
continue
|
||||
run_count += 1
|
||||
if finish:
|
||||
break
|
||||
|
||||
return self.state.TrailblazePower < self.combat_wave_cost
|
||||
logger.attr('CombatRunCount', run_count)
|
||||
return run_count
|
||||
|
@ -94,6 +94,7 @@ class CombatState(UI):
|
||||
self._combat_auto_checked = True
|
||||
else:
|
||||
if self._combat_click_interval.reached():
|
||||
self.device.image_save()
|
||||
self.device.click(COMBAT_AUTO)
|
||||
self._combat_click_interval.reset()
|
||||
return True
|
||||
|
@ -25,6 +25,11 @@ class SupportCharacter:
|
||||
# __bool__ is called when use an object of the class in a boolean context
|
||||
return self.button is not None
|
||||
|
||||
def __str__(self):
|
||||
return f'SupportCharacter({self.name})'
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def _scale_character(self):
|
||||
"""
|
||||
Returns:
|
||||
@ -154,7 +159,10 @@ class CombatSupport(UI):
|
||||
name=COMBAT_SUPPORT_LIST_SCROLL.name)
|
||||
if scroll.appear(main=self):
|
||||
if not scroll.at_bottom(main=self):
|
||||
# Dropdown to load the entire support list, so large threshold is acceptable
|
||||
scroll.drag_threshold, backup = 0.2, scroll.drag_threshold
|
||||
scroll.set_bottom(main=self)
|
||||
scroll.drag_threshold = backup
|
||||
scroll.set_top(main=self)
|
||||
|
||||
logger.info("Searching support")
|
||||
|
@ -99,6 +99,7 @@ class DailyQuestUI(DungeonUI):
|
||||
results = [result.matched_keyword for result in results]
|
||||
logger.info("Daily quests recognition complete")
|
||||
logger.info(f"Daily quests: {results}")
|
||||
self.config.stored.DailyQuest.write_quests(results)
|
||||
return results
|
||||
|
||||
def _get_quest_reward(self, skip_first_screenshot=True):
|
||||
@ -128,7 +129,7 @@ class DailyQuestUI(DungeonUI):
|
||||
|
||||
def _get_active_point_reward(self, skip_first_screenshot=True):
|
||||
def get_active():
|
||||
for button in [
|
||||
for b in [
|
||||
ACTIVE_POINTS_1_UNLOCK,
|
||||
ACTIVE_POINTS_2_UNLOCK,
|
||||
ACTIVE_POINTS_3_UNLOCK,
|
||||
@ -136,8 +137,8 @@ class DailyQuestUI(DungeonUI):
|
||||
ACTIVE_POINTS_5_UNLOCK
|
||||
]:
|
||||
# Black gift icon
|
||||
if self.image_color_count(button, color=(61, 53, 53), threshold=221, count=100):
|
||||
return button
|
||||
if self.image_color_count(b, color=(61, 53, 53), threshold=221, count=100):
|
||||
return b
|
||||
return None
|
||||
|
||||
interval = Timer(2)
|
||||
@ -156,6 +157,26 @@ class DailyQuestUI(DungeonUI):
|
||||
self.device.click(active)
|
||||
interval.reset()
|
||||
|
||||
# Write stored
|
||||
point = 0
|
||||
for progress, button in zip(
|
||||
[100, 200, 300, 400, 500],
|
||||
[
|
||||
ACTIVE_POINTS_1_CHECKED,
|
||||
ACTIVE_POINTS_2_CHECKED,
|
||||
ACTIVE_POINTS_3_CHECKED,
|
||||
ACTIVE_POINTS_4_CHECKED,
|
||||
ACTIVE_POINTS_5_CHECKED
|
||||
]
|
||||
):
|
||||
if self.appear(button):
|
||||
point = progress
|
||||
logger.attr('Daily activity', point)
|
||||
with self.config.multi_set():
|
||||
self.config.stored.DailyActivity.set(point)
|
||||
if point == 500:
|
||||
self.config.stored.DailyQuest.write_quests([])
|
||||
|
||||
def get_daily_rewards(self):
|
||||
"""
|
||||
Returns:
|
||||
|
@ -13,6 +13,16 @@ DOUBLE_CALYX_EVENT_TAG = ButtonWrapper(
|
||||
button=(329, 224, 425, 392),
|
||||
),
|
||||
)
|
||||
DOUBLE_RELIC_EVENT_TAG = ButtonWrapper(
|
||||
name='DOUBLE_RELIC_EVENT_TAG',
|
||||
share=Button(
|
||||
file='./assets/share/dungeon/event/DOUBLE_RELIC_EVENT_TAG.png',
|
||||
area=(329, 505, 425, 589),
|
||||
search=(309, 485, 445, 609),
|
||||
color=(211, 206, 198),
|
||||
button=(329, 505, 425, 589),
|
||||
),
|
||||
)
|
||||
OCR_DOUBLE_EVENT_REMAIN = ButtonWrapper(
|
||||
name='OCR_DOUBLE_EVENT_REMAIN',
|
||||
share=Button(
|
||||
|
@ -1,50 +1,210 @@
|
||||
from module.base.utils import area_offset
|
||||
from module.logger import logger
|
||||
from tasks.combat.combat import Combat
|
||||
from tasks.daily.keywords import KEYWORDS_DAILY_QUEST
|
||||
from tasks.dungeon.event import DungeonEvent
|
||||
from tasks.dungeon.keywords import DungeonList, KEYWORDS_DUNGEON_LIST, KEYWORDS_DUNGEON_TAB
|
||||
from tasks.dungeon.ui import DungeonUI
|
||||
|
||||
|
||||
class Dungeon(DungeonUI, DungeonEvent, Combat):
|
||||
def run(self, dungeon: DungeonList = None, team: int = None, use_support: str = None, is_daily: bool = False,
|
||||
support_character: str = None):
|
||||
if dungeon is None:
|
||||
dungeon = DungeonList.find(self.config.Dungeon_Name)
|
||||
called_daily_support = False
|
||||
achieved_daily_quest = False
|
||||
daily_quests = []
|
||||
|
||||
def _dungeon_run(self, dungeon: DungeonList, team: int = None, wave_limit: int = 0, support_character: str = None,
|
||||
skip_ui_switch: bool = False):
|
||||
"""
|
||||
Args:
|
||||
dungeon:
|
||||
team: 1 to 6.
|
||||
wave_limit: Limit combat runs, 0 means no limit.
|
||||
support_character: Support character name
|
||||
skip_ui_switch: True if already at dungeon aside
|
||||
|
||||
Returns:
|
||||
int: Run count
|
||||
|
||||
Pages:
|
||||
in: Any
|
||||
out: page_main
|
||||
"""
|
||||
if team is None:
|
||||
team = self.config.Dungeon_Team
|
||||
if use_support is None:
|
||||
use_support = self.config.Dungeon_Support
|
||||
if support_character is None:
|
||||
support_character = self.config.Dungeon_SupportCharacter if use_support == "always_use" or use_support == "when_daily" and is_daily else None
|
||||
if support_character is None and self.config.DungeonSupport_Use == 'always_use':
|
||||
support_character = self.config.DungeonSupport_Character
|
||||
|
||||
# UI switches
|
||||
switched = self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||
if not switched:
|
||||
# Nav must at top, reset nav states
|
||||
self.ui_goto_main()
|
||||
logger.hr('Dungeon run', level=1)
|
||||
logger.info(f'Dungeon: {dungeon}, team={team}, wave_limit={wave_limit}, support_character={support_character}')
|
||||
|
||||
if not skip_ui_switch:
|
||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||
self.dungeon_goto(dungeon)
|
||||
|
||||
# Check double events
|
||||
if self.config.Dungeon_NameAtDoubleCalyx != 'do_not_participate' and self.has_double_calyx_event():
|
||||
calyx = DungeonList.find(self.config.Dungeon_NameAtDoubleCalyx)
|
||||
self._dungeon_nav_goto(calyx)
|
||||
if remain := self.get_double_event_remain():
|
||||
self.dungeon_goto(calyx)
|
||||
if self.combat(team, wave_limit=remain, support_character=support_character):
|
||||
self.delay_dungeon_task(calyx)
|
||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||
if dungeon == KEYWORDS_DUNGEON_LIST.Stagnant_Shadow_Blaze:
|
||||
if self.handle_destructible_around_blaze():
|
||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||
self.dungeon_goto(dungeon)
|
||||
|
||||
# Combat
|
||||
self.dungeon_goto(dungeon)
|
||||
count = self.combat(team=team, wave_limit=wave_limit, support_character=support_character)
|
||||
|
||||
if dungeon == KEYWORDS_DUNGEON_LIST.Stagnant_Shadow_Blaze:
|
||||
if self.handle_destructible_around_blaze():
|
||||
# Update quest states
|
||||
if dungeon.is_Calyx_Golden \
|
||||
and KEYWORDS_DAILY_QUEST.Clear_Calyx_Golden_1_times in self.daily_quests:
|
||||
logger.info('Achieved daily quest Clear_Calyx_Golden_1_times')
|
||||
self.achieved_daily_quest = True
|
||||
if dungeon.is_Calyx_Crimson \
|
||||
and KEYWORDS_DAILY_QUEST.Complete_Calyx_Crimson_1_time in self.daily_quests:
|
||||
logger.info('Achieve daily quest Complete_Calyx_Crimson_1_time')
|
||||
self.achieved_daily_quest = True
|
||||
if dungeon.is_Stagnant_Shadow \
|
||||
and KEYWORDS_DAILY_QUEST.Clear_Stagnant_Shadow_1_times in self.daily_quests:
|
||||
logger.info('Achieve daily quest Clear_Stagnant_Shadow_1_times')
|
||||
self.achieved_daily_quest = True
|
||||
if dungeon.is_Cavern_of_Corrosion \
|
||||
and KEYWORDS_DAILY_QUEST.Clear_Cavern_of_Corrosion_1_times in self.daily_quests:
|
||||
logger.info('Achieve daily quest Clear_Cavern_of_Corrosion_1_times')
|
||||
self.achieved_daily_quest = True
|
||||
if support_character is not None:
|
||||
self.called_daily_support = True
|
||||
if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_support_characters_1_time:
|
||||
logger.info('Achieve daily quest Obtain_victory_in_combat_with_support_characters_1_time')
|
||||
self.achieved_daily_quest = True
|
||||
|
||||
# Check stamina, this may stop current task
|
||||
if self.is_stamina_exhausted():
|
||||
self.delay_dungeon_task(dungeon)
|
||||
return count
|
||||
|
||||
def dungeon_run(
|
||||
self, dungeon: DungeonList, team: int = None, wave_limit: int = 0, support_character: str = None):
|
||||
"""
|
||||
Run dungeon, and handle daily support
|
||||
|
||||
Args:
|
||||
dungeon:
|
||||
team: 1 to 6.
|
||||
wave_limit: Limit combat runs, 0 means no limit.
|
||||
support_character: Support character name
|
||||
|
||||
Returns:
|
||||
int: Run count
|
||||
|
||||
Pages:
|
||||
in: Any
|
||||
out: page_main
|
||||
"""
|
||||
require = self.require_compulsory_support()
|
||||
if require:
|
||||
logger.info('Run once with support')
|
||||
count = self._dungeon_run(dungeon=dungeon, team=team, wave_limit=1,
|
||||
support_character=self.config.DungeonSupport_Character)
|
||||
|
||||
logger.info('Run the rest waves without compulsory support')
|
||||
if wave_limit >= 2 or wave_limit == 0:
|
||||
# Already at page_name with DUNGEON_COMBAT_INTERACT
|
||||
if wave_limit >= 2:
|
||||
wave_limit -= 1
|
||||
count += self._dungeon_run(dungeon=dungeon, team=team, wave_limit=wave_limit,
|
||||
support_character=support_character, skip_ui_switch=True)
|
||||
|
||||
return count
|
||||
|
||||
else:
|
||||
# Normal run
|
||||
return self._dungeon_run(dungeon=dungeon, team=team, wave_limit=wave_limit,
|
||||
support_character=support_character)
|
||||
|
||||
def run(self):
|
||||
self.config.update_daily_quests()
|
||||
self.called_daily_support = False
|
||||
self.achieved_daily_quest = False
|
||||
self.daily_quests = self.config.stored.DailyQuest.load_quests()
|
||||
|
||||
# Update double event records
|
||||
if self.config.stored.DungeonDouble.is_expired():
|
||||
logger.info('Get dungeon double remains')
|
||||
# UI switches
|
||||
switched = self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||
if not switched:
|
||||
# Nav must at top, reset nav states
|
||||
self.ui_goto_main()
|
||||
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)
|
||||
self.dungeon_goto(dungeon)
|
||||
# Check remains
|
||||
calyx = 0
|
||||
relic = 0
|
||||
if self.has_double_calyx_event():
|
||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_LIST.Calyx_Golden_Treasures)
|
||||
calyx = self.get_double_event_remain()
|
||||
if self.has_double_relic_event():
|
||||
self._dungeon_nav_goto(KEYWORDS_DUNGEON_LIST.Cavern_of_Corrosion_Path_of_Gelid_Wind)
|
||||
relic = self.get_double_event_remain()
|
||||
with self.config.multi_set():
|
||||
self.config.stored.DungeonDouble.calyx = calyx
|
||||
self.config.stored.DungeonDouble.relic = relic
|
||||
|
||||
self.combat(team=team, support_character=support_character)
|
||||
self.delay_dungeon_task(dungeon)
|
||||
# Run double events
|
||||
ran_calyx_golden = False
|
||||
ran_calyx_crimson = False
|
||||
ran_cavern_of_corrosion = False
|
||||
# Double calyx
|
||||
if self.config.Dungeon_NameAtDoubleCalyx != 'do_not_participate' \
|
||||
and self.config.stored.DungeonDouble.calyx > 0:
|
||||
logger.info('Run double calyx')
|
||||
dungeon = DungeonList.find(self.config.Dungeon_NameAtDoubleCalyx)
|
||||
if self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.calyx):
|
||||
if dungeon.is_Calyx_Golden:
|
||||
ran_calyx_golden = True
|
||||
if dungeon.is_Calyx_Crimson:
|
||||
ran_calyx_crimson = True
|
||||
# Double relic
|
||||
if self.config.Dungeon_NameAtDoubleRelic != 'do_not_participate' \
|
||||
and self.config.stored.DungeonDouble.relic > 0:
|
||||
logger.info('Run double relic')
|
||||
dungeon = DungeonList.find(self.config.Dungeon_NameAtDoubleRelic)
|
||||
if self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.relic):
|
||||
ran_cavern_of_corrosion = True
|
||||
|
||||
# Dungeon to clear all trailblaze power
|
||||
final = DungeonList.find(self.config.Dungeon_Name)
|
||||
|
||||
# Run dungeon that required by daily quests
|
||||
# Calyx_Golden
|
||||
if KEYWORDS_DAILY_QUEST.Clear_Calyx_Golden_1_times in self.daily_quests \
|
||||
and self.config.DungeonDaily_CalyxGolden != 'do_not_achieve' \
|
||||
and not final.is_Calyx_Golden \
|
||||
and not ran_calyx_golden:
|
||||
logger.info('Run Calyx_Golden once')
|
||||
dungeon = DungeonList.find(self.config.DungeonDaily_CalyxGolden)
|
||||
self.dungeon_run(dungeon=dungeon, wave_limit=1)
|
||||
# Calyx_Crimson
|
||||
if KEYWORDS_DAILY_QUEST.Complete_Calyx_Crimson_1_time in self.daily_quests \
|
||||
and self.config.DungeonDaily_CalyxCrimson != 'do_not_achieve' \
|
||||
and not final.is_Calyx_Crimson \
|
||||
and not ran_calyx_crimson:
|
||||
logger.info('Run Calyx_Crimson once')
|
||||
dungeon = DungeonList.find(self.config.DungeonDaily_CalyxCrimson)
|
||||
self.dungeon_run(dungeon=dungeon, wave_limit=1)
|
||||
# Stagnant_Shadow
|
||||
if KEYWORDS_DAILY_QUEST.Clear_Stagnant_Shadow_1_times in self.daily_quests \
|
||||
and self.config.DungeonDaily_StagnantShadow != 'do_not_achieve' \
|
||||
and not final.is_Stagnant_Shadow:
|
||||
logger.info('Run Stagnant_Shadow once')
|
||||
dungeon = DungeonList.find(self.config.DungeonDaily_StagnantShadow)
|
||||
self.dungeon_run(dungeon=dungeon, wave_limit=1)
|
||||
# Cavern_of_Corrosion
|
||||
if KEYWORDS_DAILY_QUEST.Clear_Cavern_of_Corrosion_1_times in self.daily_quests \
|
||||
and self.config.DungeonDaily_CavernOfCorrosion != 'do_not_achieve' \
|
||||
and not final.is_Cavern_of_Corrosion \
|
||||
and not ran_cavern_of_corrosion:
|
||||
logger.info('Run Cavern_of_Corrosion once')
|
||||
dungeon = DungeonList.find(self.config.DungeonDaily_CavernOfCorrosion)
|
||||
self.dungeon_run(dungeon=dungeon, wave_limit=1)
|
||||
|
||||
# Combat
|
||||
self.dungeon_run(final)
|
||||
self.delay_dungeon_task(final)
|
||||
|
||||
def delay_dungeon_task(self, dungeon):
|
||||
if dungeon.is_Cavern_of_Corrosion:
|
||||
@ -54,7 +214,12 @@ class Dungeon(DungeonUI, DungeonEvent, Combat):
|
||||
# Recover 1 trailbaze power each 6 minutes
|
||||
cover = max(limit - self.state.TrailblazePower, 0) * 6
|
||||
logger.info(f'Currently has {self.state.TrailblazePower} need {cover} minutes to reach {limit}')
|
||||
self.config.task_delay(minute=cover)
|
||||
logger.attr('achieved_daily_quest', self.achieved_daily_quest)
|
||||
with self.config.multi_set():
|
||||
if self.achieved_daily_quest:
|
||||
self.config.task_call('DailyQuest')
|
||||
self.config.task_delay(minute=cover)
|
||||
self.config.task_stop()
|
||||
|
||||
def handle_destructible_around_blaze(self):
|
||||
"""
|
||||
@ -95,3 +260,23 @@ class Dungeon(DungeonUI, DungeonEvent, Combat):
|
||||
break
|
||||
|
||||
return handled
|
||||
|
||||
def require_compulsory_support(self) -> bool:
|
||||
require = False
|
||||
|
||||
if not self.config.stored.DailyActivity.is_full():
|
||||
if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_support_characters_1_time \
|
||||
in self.daily_quests:
|
||||
require = True
|
||||
|
||||
logger.attr('called_daily_support', self.called_daily_support)
|
||||
if self.called_daily_support:
|
||||
require = False
|
||||
|
||||
# Not required, cause any dungeon run will achieve the quest
|
||||
logger.attr('DungeonSupport_Use', self.config.DungeonSupport_Use)
|
||||
if self.config.DungeonSupport_Use == 'always_use':
|
||||
require = False
|
||||
|
||||
logger.attr('Require compulsory support', require)
|
||||
return require
|
||||
|
@ -1,7 +1,11 @@
|
||||
from module.logger import logger
|
||||
from module.ocr.ocr import DigitCounter
|
||||
from tasks.base.ui import UI
|
||||
from tasks.dungeon.assets.assets_dungeon_event import DOUBLE_CALYX_EVENT_TAG, OCR_DOUBLE_EVENT_REMAIN
|
||||
from tasks.dungeon.assets.assets_dungeon_event import (
|
||||
DOUBLE_CALYX_EVENT_TAG,
|
||||
DOUBLE_RELIC_EVENT_TAG,
|
||||
OCR_DOUBLE_EVENT_REMAIN
|
||||
)
|
||||
|
||||
|
||||
class DungeonEvent(UI):
|
||||
@ -11,9 +15,20 @@ class DungeonEvent(UI):
|
||||
in: page_guide, Survival_Index, nav at top
|
||||
"""
|
||||
has = self.image_color_count(DOUBLE_CALYX_EVENT_TAG, color=(252, 209, 123), threshold=221, count=50)
|
||||
has |= self.image_color_count(DOUBLE_CALYX_EVENT_TAG, color=(252, 251, 140), threshold=221, count=50)
|
||||
logger.attr('Double calyx', has)
|
||||
return has
|
||||
|
||||
def has_double_relic_event(self) -> bool:
|
||||
"""
|
||||
Pages:
|
||||
in: page_guide, Survival_Index, nav at top
|
||||
"""
|
||||
has = self.image_color_count(DOUBLE_RELIC_EVENT_TAG, color=(252, 209, 123), threshold=221, count=50)
|
||||
has |= self.image_color_count(DOUBLE_RELIC_EVENT_TAG, color=(252, 251, 140), threshold=221, count=50)
|
||||
logger.attr('Double relic', has)
|
||||
return has
|
||||
|
||||
def get_double_event_remain(self) -> int:
|
||||
"""
|
||||
Pages:
|
||||
@ -21,7 +36,7 @@ class DungeonEvent(UI):
|
||||
"""
|
||||
ocr = DigitCounter(OCR_DOUBLE_EVENT_REMAIN)
|
||||
remain, _, total = ocr.ocr_single_line(self.device.image)
|
||||
if total != 12:
|
||||
if total not in [3, 12]:
|
||||
logger.warning(f'Invalid double event remain')
|
||||
remain = 0
|
||||
logger.attr('Double event remain', remain)
|
||||
|
@ -28,6 +28,10 @@ class DungeonList(Keyword):
|
||||
def is_Calyx_Crimson(self):
|
||||
return 'Calyx_Crimson' in self.name
|
||||
|
||||
@cached_property
|
||||
def is_Calyx(self):
|
||||
return self.is_Calyx_Golden or self.is_Calyx_Crimson
|
||||
|
||||
@cached_property
|
||||
def is_Stagnant_Shadow(self):
|
||||
return 'Stagnant_Shadow' in self.name
|
||||
|
@ -200,4 +200,4 @@ class ForgottenHallUI(DungeonUI):
|
||||
if self.match_template_color(DUNGEON_ENTER_CHECKED):
|
||||
logger.info("Forgotten hall dungeon entered")
|
||||
break
|
||||
joystick.handle_map_run()
|
||||
joystick.handle_map_2x_run()
|
||||
|
@ -33,6 +33,16 @@ JOYSTICK = ButtonWrapper(
|
||||
button=(234, 546, 262, 574),
|
||||
),
|
||||
)
|
||||
ROTATION_SWIPE_AREA = ButtonWrapper(
|
||||
name='ROTATION_SWIPE_AREA',
|
||||
share=Button(
|
||||
file='./assets/share/map/control/ROTATION_SWIPE_AREA.png',
|
||||
area=(264, 87, 990, 219),
|
||||
search=(244, 67, 1010, 239),
|
||||
color=(255, 255, 255),
|
||||
button=(264, 87, 990, 219),
|
||||
),
|
||||
)
|
||||
RUN_BUTTON = ButtonWrapper(
|
||||
name='RUN_BUTTON',
|
||||
share=Button(
|
||||
|
265
tasks/map/control/control.py
Normal file
265
tasks/map/control/control.py
Normal file
@ -0,0 +1,265 @@
|
||||
from functools import cached_property
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.logger import logger
|
||||
from tasks.map.assets.assets_map_control import ROTATION_SWIPE_AREA
|
||||
from tasks.map.control.joystick import JoystickContact, MapControlJoystick
|
||||
from tasks.map.control.waypoint import Waypoint, WaypointRun, WaypointStraightRun, ensure_waypoint
|
||||
from tasks.map.minimap.minimap import Minimap
|
||||
from tasks.map.resource.const import diff_to_180_180
|
||||
|
||||
|
||||
class MapControl(MapControlJoystick):
|
||||
@cached_property
|
||||
def minimap(self) -> Minimap:
|
||||
return Minimap()
|
||||
|
||||
def handle_rotation_set(self, target, threshold=15):
|
||||
"""
|
||||
Set rotation while running.
|
||||
self.minimap.update_rotation() must be called first.
|
||||
|
||||
Args:
|
||||
target: Target degree (0~360)
|
||||
threshold:
|
||||
|
||||
Returns:
|
||||
bool: If swiped rotation
|
||||
"""
|
||||
if self.minimap.is_rotation_near(target, threshold=threshold):
|
||||
return False
|
||||
|
||||
# if abs(self.minimap.rotation_diff(target)) > 60:
|
||||
# self.device.image_save()
|
||||
# exit(1)
|
||||
|
||||
logger.info(f'Rotation set: {target}')
|
||||
diff = self.minimap.rotation_diff(target) * self.minimap.ROTATION_SWIPE_MULTIPLY
|
||||
diff = min(diff, self.minimap.ROTATION_SWIPE_MAX_DISTANCE)
|
||||
diff = max(diff, -self.minimap.ROTATION_SWIPE_MAX_DISTANCE)
|
||||
|
||||
self.device.swipe_vector((-diff, 0), box=ROTATION_SWIPE_AREA.area, duration=(0.2, 0.5))
|
||||
return True
|
||||
|
||||
def rotation_set(self, target, threshold=15, skip_first_screenshot=False):
|
||||
"""
|
||||
Set rotation while standing.
|
||||
|
||||
Args:
|
||||
target: Target degree (0~360)
|
||||
threshold:
|
||||
skip_first_screenshot:
|
||||
|
||||
Returns:
|
||||
bool: If swiped rotation
|
||||
"""
|
||||
interval = Timer(1, count=2)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
self.minimap.update_rotation(self.device.image)
|
||||
self.minimap.log_minimap()
|
||||
|
||||
# End
|
||||
if self.minimap.is_rotation_near(target, threshold=threshold):
|
||||
logger.info(f'Rotation is now at: {target}')
|
||||
break
|
||||
|
||||
if interval.reached():
|
||||
if self.handle_rotation_set(target, threshold=threshold):
|
||||
interval.reset()
|
||||
continue
|
||||
|
||||
def _goto(
|
||||
self,
|
||||
contact: JoystickContact,
|
||||
waypoint: Waypoint,
|
||||
end_point_opt=True,
|
||||
skip_first_screenshot=False
|
||||
):
|
||||
"""
|
||||
Point to point walk.
|
||||
|
||||
Args:
|
||||
contact:
|
||||
JoystickContact, must be wrapped with:
|
||||
`with JoystickContact(self) as contact:`
|
||||
waypoint:
|
||||
Position to goto, (x, y)
|
||||
end_point_opt:
|
||||
True to enable endpoint optimizations,
|
||||
character will smoothly approach target position
|
||||
skip_first_screenshot:
|
||||
"""
|
||||
logger.hr('Goto', level=2)
|
||||
logger.info(f'Goto {waypoint}')
|
||||
self.device.stuck_record_clear()
|
||||
self.device.click_record_clear()
|
||||
|
||||
end_point_opt = end_point_opt and waypoint.end_point_opt
|
||||
allow_2x_run = waypoint.speed in ['2x_run']
|
||||
allow_straight_run = waypoint.speed in ['2x_run', 'straight_run']
|
||||
allow_run = waypoint.speed in ['2x_run', 'straight_run', 'run']
|
||||
allow_rotation_set = True
|
||||
last_rotation = 0
|
||||
|
||||
direction_interval = Timer(0.5, count=1)
|
||||
rotation_interval = Timer(0.3, count=1)
|
||||
while 1:
|
||||
if skip_first_screenshot:
|
||||
skip_first_screenshot = False
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
# Update
|
||||
self.minimap.update(self.device.image)
|
||||
|
||||
# Arrive
|
||||
if self.minimap.is_position_near(waypoint.position, threshold=waypoint.get_threshold(end_point_opt)):
|
||||
logger.info(f'Arrive {waypoint}')
|
||||
break
|
||||
|
||||
# Switch run case
|
||||
diff = self.minimap.position_diff(waypoint.position)
|
||||
if end_point_opt:
|
||||
if allow_2x_run and diff < 20:
|
||||
logger.info(f'Approaching target, diff={round(diff, 1)}, disallow 2x_run')
|
||||
allow_2x_run = False
|
||||
if allow_straight_run and diff < 15:
|
||||
logger.info(f'Approaching target, diff={round(diff, 1)}, disallow straight_run')
|
||||
direction_interval = Timer(0.2)
|
||||
self._map_2x_run_timer.reset()
|
||||
allow_straight_run = False
|
||||
if allow_run and diff < 7:
|
||||
logger.info(f'Approaching target, diff={round(diff, 1)}, disallow run')
|
||||
direction_interval = Timer(0.2)
|
||||
allow_run = False
|
||||
|
||||
# Control
|
||||
direction = self.minimap.position2direction(waypoint.position)
|
||||
if allow_2x_run:
|
||||
# Run with 2x_run button
|
||||
# - Set rotation once
|
||||
# - Continuous fine-tuning direction
|
||||
# - Enable 2x_run
|
||||
if allow_rotation_set:
|
||||
# Cache rotation cause rotation detection has a higher error rate
|
||||
last_rotation = self.minimap.rotation
|
||||
if self.minimap.is_rotation_near(direction, threshold=10):
|
||||
logger.info(f'Already at target rotation, '
|
||||
f'current={last_rotation}, target={direction}, disallow rotation_set')
|
||||
allow_rotation_set = False
|
||||
if allow_rotation_set and rotation_interval.reached():
|
||||
if self.handle_rotation_set(direction, threshold=10):
|
||||
rotation_interval.reset()
|
||||
direction_interval.reset()
|
||||
if direction_interval.reached():
|
||||
contact.set(direction=diff_to_180_180(direction - last_rotation), run=True)
|
||||
direction_interval.reset()
|
||||
self.handle_map_2x_run(run=True)
|
||||
elif allow_straight_run:
|
||||
# Run with 2x_run button
|
||||
# - Set rotation once
|
||||
# - Continuous fine-tuning direction
|
||||
# - Disable 2x_run
|
||||
if allow_rotation_set:
|
||||
# Cache rotation cause rotation detection has a higher error rate
|
||||
last_rotation = self.minimap.rotation
|
||||
if self.minimap.is_rotation_near(direction, threshold=10):
|
||||
logger.info(f'Already at target rotation, '
|
||||
f'current={last_rotation}, target={direction}, disallow rotation_set')
|
||||
allow_rotation_set = False
|
||||
if allow_rotation_set and rotation_interval.reached():
|
||||
if self.handle_rotation_set(direction, threshold=10):
|
||||
rotation_interval.reset()
|
||||
direction_interval.reset()
|
||||
if direction_interval.reached():
|
||||
contact.set(direction=diff_to_180_180(direction - last_rotation), run=True)
|
||||
direction_interval.reset()
|
||||
self.handle_map_2x_run(run=False)
|
||||
elif allow_run:
|
||||
# Run
|
||||
# - No rotation set
|
||||
# - Continuous fine-tuning direction
|
||||
# - Disable 2x_run
|
||||
if allow_rotation_set:
|
||||
last_rotation = self.minimap.rotation
|
||||
allow_rotation_set = False
|
||||
if direction_interval.reached():
|
||||
contact.set(direction=diff_to_180_180(direction - last_rotation), run=True)
|
||||
self.handle_map_2x_run(run=False)
|
||||
else:
|
||||
# Walk
|
||||
# - Continuous fine-tuning direction
|
||||
# - Disable 2x_run
|
||||
if allow_rotation_set:
|
||||
last_rotation = self.minimap.rotation
|
||||
allow_rotation_set = False
|
||||
if direction_interval.reached():
|
||||
contact.set(direction=diff_to_180_180(direction - last_rotation), run=False)
|
||||
direction_interval.reset()
|
||||
self.handle_map_2x_run(run=False)
|
||||
|
||||
def goto(
|
||||
self,
|
||||
waypoints,
|
||||
skip_first_screenshot=True
|
||||
):
|
||||
"""
|
||||
Go along a list of position, or goto target position
|
||||
|
||||
Args:
|
||||
waypoints:
|
||||
position (x, y) to goto, or a list of position to go along.
|
||||
Waypoint object to goto, or a list of Waypoint objects to go along.
|
||||
|
||||
skip_first_screenshot:
|
||||
"""
|
||||
logger.hr('Goto', level=1)
|
||||
if not isinstance(waypoints, list):
|
||||
waypoints = [waypoints]
|
||||
waypoints = [ensure_waypoint(point) for point in waypoints]
|
||||
logger.info(f'Go along {len(waypoints)} waypoints')
|
||||
end_list = [False for _ in waypoints]
|
||||
end_list[-1] = True
|
||||
|
||||
with JoystickContact(self) as contact:
|
||||
for point, end in zip(waypoints, end_list):
|
||||
point: Waypoint
|
||||
self._goto(
|
||||
contact=contact,
|
||||
waypoint=point,
|
||||
end_point_opt=end,
|
||||
skip_first_screenshot=skip_first_screenshot
|
||||
)
|
||||
skip_first_screenshot = True
|
||||
|
||||
end_point = waypoints[-1]
|
||||
if end_point.end_point_rotation is not None:
|
||||
self.rotation_set(end_point.end_point_rotation, threshold=end_point.end_point_rotation_threshold)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Control test in Himeko trail
|
||||
# Must manually enter Himeko trail first and dismiss popup
|
||||
self = MapControl('alas')
|
||||
self.minimap.set_plane('Jarilo_BackwaterPass', floor='F1')
|
||||
self.device.screenshot()
|
||||
self.minimap.init_position((519, 359))
|
||||
# Visit 3 items
|
||||
self.goto([
|
||||
WaypointRun((577.6, 363.4)),
|
||||
])
|
||||
self.goto([
|
||||
WaypointStraightRun((577.5, 369.4), end_point_rotation=200),
|
||||
])
|
||||
self.goto([
|
||||
WaypointRun((581.5, 387.3)),
|
||||
WaypointRun((577.4, 411.5)),
|
||||
])
|
||||
# Goto boss
|
||||
self.goto([
|
||||
WaypointStraightRun((607.6, 425.3)),
|
||||
])
|
@ -1,15 +1,135 @@
|
||||
import math
|
||||
from functools import cached_property
|
||||
|
||||
from module.base.timer import Timer
|
||||
from module.device.method.maatouch import MaatouchBuilder
|
||||
from module.device.method.minitouch import CommandBuilder, insert_swipe, random_normal_distribution
|
||||
from module.exception import ScriptError
|
||||
from module.logger import logger
|
||||
from tasks.base.ui import UI
|
||||
from tasks.map.assets.assets_map_control import *
|
||||
|
||||
|
||||
class JoystickContact:
|
||||
CENTER = (JOYSTICK.area[0] + JOYSTICK.area[2]) / 2, (JOYSTICK.area[1] + JOYSTICK.area[3]) / 2
|
||||
# Minimum radius 49px
|
||||
RADIUS_WALK = (55, 65)
|
||||
# Minimum radius 103px
|
||||
RADIUS_RUN = (105, 115)
|
||||
|
||||
def __init__(self, main):
|
||||
"""
|
||||
Args:
|
||||
main (MapControlJoystick):
|
||||
"""
|
||||
self.main = main
|
||||
self.prev_point = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Lift finger when:
|
||||
- Walk event ends, JoystickContact ends
|
||||
- Any error is raised
|
||||
Can not lift finger when:
|
||||
- Process is force terminated
|
||||
"""
|
||||
builder = self.builder
|
||||
if self.is_downed:
|
||||
builder.up().commit()
|
||||
builder.send()
|
||||
logger.info('JoystickContact ends')
|
||||
else:
|
||||
logger.info('JoystickContact ends but it was never downed')
|
||||
|
||||
@property
|
||||
def is_downed(self):
|
||||
return self.prev_point is not None
|
||||
|
||||
@cached_property
|
||||
def builder(self):
|
||||
"""
|
||||
Initialize a command builder
|
||||
"""
|
||||
method = self.main.config.Emulator_ControlMethod
|
||||
if method == 'MaaTouch':
|
||||
# Get the very first builder to initialize MaaTouch
|
||||
_ = self.main.device.maatouch_builder
|
||||
builder = MaatouchBuilder(self.main.device, contact=1)
|
||||
elif method == 'minitouch':
|
||||
# Get the very first builder to initialize minitouch
|
||||
_ = self.main.device.minitouch_builder
|
||||
builder = CommandBuilder(self.main.device, contact=1)
|
||||
else:
|
||||
raise ScriptError(f'Control method {method} does not support multi-finger, '
|
||||
f'please use MaaTouch or minitouch instead')
|
||||
|
||||
# def empty_func():
|
||||
# pass
|
||||
#
|
||||
# # No clear()
|
||||
# builder.clear = empty_func
|
||||
# No delay
|
||||
builder.DEFAULT_DELAY = 0.
|
||||
|
||||
return builder
|
||||
|
||||
@classmethod
|
||||
def direction2screen(cls, direction, run=True):
|
||||
"""
|
||||
Args:
|
||||
direction (int, float): Direction to goto (0~360)
|
||||
run: True for character running, False for walking
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: Position on screen to control joystick
|
||||
"""
|
||||
direction += random_normal_distribution(-5, 5, n=5)
|
||||
radius = cls.RADIUS_RUN if run else cls.RADIUS_WALK
|
||||
radius = random_normal_distribution(*radius, n=5)
|
||||
|
||||
direction = math.radians(direction)
|
||||
point = (
|
||||
cls.CENTER[0] + radius * math.sin(direction),
|
||||
cls.CENTER[1] - radius * math.cos(direction),
|
||||
)
|
||||
point = (int(round(point[0])), int(round(point[1])))
|
||||
return point
|
||||
|
||||
def set(self, direction, run=True):
|
||||
"""
|
||||
Set joystick to given position
|
||||
|
||||
Args:
|
||||
direction (int, float): Direction to goto (0~360)
|
||||
run: True for character running, False for walking
|
||||
"""
|
||||
logger.info(f'JoystickContact set to {direction}')
|
||||
point = JoystickContact.direction2screen(direction, run=run)
|
||||
builder = self.builder
|
||||
|
||||
if self.is_downed:
|
||||
points = insert_swipe(p0=self.prev_point, p3=point, speed=20)
|
||||
for point in points[1:]:
|
||||
builder.move(*point).commit().wait(10)
|
||||
builder.send()
|
||||
else:
|
||||
builder.down(*point).commit()
|
||||
builder.send()
|
||||
# Character starts moving, RUN button is still unavailable in a short time.
|
||||
# Assume available in 0.3s
|
||||
# We still have reties if 0.3s is incorrect.
|
||||
self.main._map_2x_run_timer.set_current(0.7)
|
||||
|
||||
self.prev_point = point
|
||||
|
||||
|
||||
class MapControlJoystick(UI):
|
||||
_map_A_timer = Timer(1)
|
||||
_map_E_timer = Timer(1)
|
||||
_map_run_timer = Timer(1)
|
||||
_map_2x_run_timer = Timer(1)
|
||||
|
||||
@cached_property
|
||||
def joystick_center(self) -> tuple[float, float]:
|
||||
@ -64,7 +184,7 @@ class MapControlJoystick(UI):
|
||||
|
||||
return False
|
||||
|
||||
def handle_map_run(self):
|
||||
def handle_map_2x_run(self, run=True):
|
||||
"""
|
||||
Keep character running.
|
||||
Note that RUN button can only be clicked when character is moving.
|
||||
@ -74,9 +194,13 @@ class MapControlJoystick(UI):
|
||||
"""
|
||||
is_running = self.image_color_count(RUN_BUTTON, color=(208, 183, 138), threshold=221, count=100)
|
||||
|
||||
if not is_running and self._map_run_timer.reached():
|
||||
if run and not is_running and self._map_2x_run_timer.reached():
|
||||
self.device.click(RUN_BUTTON)
|
||||
self._map_run_timer.reset()
|
||||
self._map_2x_run_timer.reset()
|
||||
return True
|
||||
if not run and is_running and self._map_2x_run_timer.reached():
|
||||
self.device.click(RUN_BUTTON)
|
||||
self._map_2x_run_timer.reset()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
79
tasks/map/control/waypoint.py
Normal file
79
tasks/map/control/waypoint.py
Normal file
@ -0,0 +1,79 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Waypoint:
|
||||
# Position to goto, (x, y)
|
||||
position: tuple
|
||||
# Position diff < threshold is considered as arrived
|
||||
# `threshold` is used first if it is set
|
||||
threshold: int = None
|
||||
# If `threshold` is not set, `waypoint_threshold` and `endpoint_threshold` are used
|
||||
waypoint_threshold: int = 10
|
||||
endpoint_threshold: int = 3
|
||||
# Max move speed, '2x_run', 'straight_run', 'run', 'walk'
|
||||
# See MapControl._goto() for details of each speed level
|
||||
speed: str = '2x_run'
|
||||
|
||||
"""
|
||||
The following attributes are only be used if this waypoint is the end point of goto()
|
||||
"""
|
||||
# True to enable endpoint optimizations, character will smoothly approach target position
|
||||
# False to stop all controls at arrive
|
||||
end_point_opt: bool = True
|
||||
# Set rotation after arrive, 0~360
|
||||
end_point_rotation: int = None
|
||||
end_point_rotation_threshold: int = 15
|
||||
|
||||
def __str__(self):
|
||||
return f'Waypoint({self.position})'
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def get_threshold(self, end):
|
||||
"""
|
||||
Args:
|
||||
end: True if this is an end point
|
||||
|
||||
Returns:
|
||||
int
|
||||
"""
|
||||
if self.threshold is not None:
|
||||
return self.threshold
|
||||
if end:
|
||||
return self.endpoint_threshold
|
||||
else:
|
||||
return self.waypoint_threshold
|
||||
|
||||
|
||||
def ensure_waypoint(point) -> Waypoint:
|
||||
"""
|
||||
Args:
|
||||
point: Position (x, y) or Waypoint object
|
||||
|
||||
Returns:
|
||||
Waypoint:
|
||||
"""
|
||||
if isinstance(point, Waypoint):
|
||||
return point
|
||||
return Waypoint(point)
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class Waypoint2xRun(Waypoint):
|
||||
speed: str = '2x_run'
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class WaypointStraightRun(Waypoint):
|
||||
speed: str = 'straight_run'
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class WaypointRun(Waypoint):
|
||||
speed: str = 'run'
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class WaypointWalk(Waypoint):
|
||||
speed: str = 'walk'
|
@ -67,7 +67,7 @@ class Minimap(MapResource):
|
||||
search_position = np.array(self.position, dtype=np.int64)
|
||||
search_position += self.POSITION_FEATURE_PAD
|
||||
search_size = np.array(image_size(local)) * self.POSITION_SEARCH_RADIUS
|
||||
search_half = (search_size // 2 * 2).astype(np.int64)
|
||||
search_half = (search_size // 2).astype(np.int64)
|
||||
search_area = area_offset((0, 0, *(search_half * 2)), offset=-search_half)
|
||||
search_area = area_offset(search_area, offset=np.multiply(search_position, self.POSITION_SEARCH_SCALE))
|
||||
search_area = np.array(search_area).astype(np.int64)
|
||||
@ -269,11 +269,12 @@ class Minimap(MapResource):
|
||||
# Extract
|
||||
minimap = self.get_minimap(image, radius=self.MINIMAP_RADIUS)
|
||||
_, _, v = cv2.split(rgb2yuv(minimap))
|
||||
image = cv2.subtract(255, v)
|
||||
|
||||
# image = cv2.GaussianBlur(image, (3, 3), 0)
|
||||
image = cv2.subtract(128, v)
|
||||
|
||||
image = cv2.GaussianBlur(image, (3, 3), 0)
|
||||
# Expand circle into rectangle
|
||||
remap = cv2.remap(image, *self.RotationRemapData, cv2.INTER_LINEAR)[d * 2 // 10:d * 6 // 10].astype(np.float32)
|
||||
remap = cv2.remap(image, *self.RotationRemapData, cv2.INTER_LINEAR)[d * 1 // 10:d * 6 // 10].astype(np.float32)
|
||||
remap = cv2.resize(remap, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR)
|
||||
# Find derivative
|
||||
gradx = cv2.Scharr(remap, cv2.CV_32F, 1, 0)
|
||||
@ -284,7 +285,7 @@ class Minimap(MapResource):
|
||||
# Magic parameters for scipy.find_peaks
|
||||
para = {
|
||||
# 'height': (50, 800),
|
||||
'height': 50,
|
||||
'height': 35,
|
||||
# 'prominence': (0, 400),
|
||||
# 'width': (0, d * scale / 20),
|
||||
# 'distance': d * scale / 18,
|
||||
@ -356,14 +357,17 @@ class Minimap(MapResource):
|
||||
self.rotation_confidence = rotation_confidence
|
||||
self.rotation = rotation
|
||||
|
||||
def update(self, image):
|
||||
def update(self, image, show_log=True):
|
||||
"""
|
||||
Update minimap, costs about 7.88ms.
|
||||
"""
|
||||
self.update_position(image)
|
||||
self.update_direction(image)
|
||||
self.update_rotation(image)
|
||||
if show_log:
|
||||
self.log_minimap()
|
||||
|
||||
def log_minimap(self):
|
||||
# MiniMap P:(567.5, 862.8) (1.00x|0.439|0.157), D:303.8 (0.253), R:304 (0.846)
|
||||
logger.info(
|
||||
f'MiniMap '
|
||||
|
@ -1,5 +1,6 @@
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from module.base.utils import load_image
|
||||
@ -16,13 +17,14 @@ class ResourceConst:
|
||||
# Downscale GIMAP and minimap for faster run
|
||||
POSITION_SEARCH_SCALE = 0.5
|
||||
# Search the area that is 1.666x minimap, about 100px in wild on GIMAP
|
||||
POSITION_SEARCH_RADIUS = 1.333
|
||||
POSITION_SEARCH_RADIUS = 1.666
|
||||
# Can't figure out why but the result_of_0.5_lookup_scale + 0.5 ~= result_of_1.0_lookup_scale
|
||||
POSITION_MOVE_PATCH = (0.5, 0.5)
|
||||
# Position starting from the upper-left corner of the template image
|
||||
# but search an area larger than map
|
||||
# MINIMAP_RADIUS * POSITION_SEARCH_RADIUS * <max_scale>
|
||||
POSITION_FEATURE_PAD = int(MINIMAP_RADIUS * POSITION_SEARCH_RADIUS * 1.5)
|
||||
# POSITION_FEATURE_PAD = int(MINIMAP_RADIUS * POSITION_SEARCH_RADIUS * 1.5)
|
||||
POSITION_FEATURE_PAD = 155
|
||||
# Must be odd, equals int(9 * POSITION_SEARCH_SCALE) + 1
|
||||
POSITION_AREA_DILATE = 5
|
||||
|
||||
@ -43,6 +45,12 @@ class ResourceConst:
|
||||
# Pad 600px, cause camera sight in game is larger than GIMAP
|
||||
BIGMAP_BORDER_PAD = int(600 * BIGMAP_SEARCH_SCALE)
|
||||
|
||||
# Swipe 400px is about 85~90 degree
|
||||
# <rotation_diff> * ROTATION_SWIPE_MULTIPLY = <distance_to_swipe>
|
||||
ROTATION_SWIPE_MULTIPLY = 400 / 85
|
||||
# Max distance in one swipe, limited in -600px~600px
|
||||
ROTATION_SWIPE_MAX_DISTANCE = 600
|
||||
|
||||
def __init__(self):
|
||||
# Usually to be 0.4~0.5
|
||||
self.position_similarity = 0.
|
||||
@ -50,6 +58,8 @@ class ResourceConst:
|
||||
self.position_similarity_local = 0.
|
||||
# Current position on GIMAP with an error of about 0.1 pixel
|
||||
self.position: tuple[float, float] = (0, 0)
|
||||
# Minimap scale factor, 1.0~1.25
|
||||
self.position_scale = 1.0
|
||||
|
||||
# Usually > 0.3
|
||||
# Warnings will be logged if similarity <= 0.8
|
||||
@ -82,3 +92,81 @@ class ResourceConst:
|
||||
file = self.filepath(file)
|
||||
print(f'Save image: {file}')
|
||||
Image.fromarray(image).save(file)
|
||||
|
||||
def position_diff(self, target):
|
||||
"""
|
||||
Args:
|
||||
target: Target position (x, y)
|
||||
|
||||
Returns:
|
||||
float: Distance to current position
|
||||
"""
|
||||
return np.linalg.norm(np.subtract(target, self.position))
|
||||
|
||||
def is_position_near(self, target, threshold=5):
|
||||
return self.position_diff(target) <= threshold
|
||||
|
||||
def position2direction(self, target):
|
||||
"""
|
||||
Args:
|
||||
target: Target position (x, y)
|
||||
|
||||
Returns:
|
||||
float: Direction from current position to target position (0~360)
|
||||
"""
|
||||
diff = np.subtract(target, self.position)
|
||||
theta = np.rad2deg(np.arccos(-diff[1] / np.linalg.norm(diff)))
|
||||
if diff[0] < 0:
|
||||
theta = 360 - theta
|
||||
return theta
|
||||
|
||||
def direction_diff(self, target):
|
||||
"""
|
||||
Args:
|
||||
target: Target degree (0~360)
|
||||
|
||||
Returns:
|
||||
float: Diff to current direction (-180~180)
|
||||
"""
|
||||
return diff_to_180_180(self.direction - target)
|
||||
|
||||
def is_direction_near(self, target, threshold=15):
|
||||
return abs(self.direction_diff(target)) <= threshold
|
||||
|
||||
def rotation_diff(self, target):
|
||||
"""
|
||||
Args:
|
||||
target: Target degree (0~360)
|
||||
|
||||
Returns:
|
||||
float: Diff to current rotation (-180~180)
|
||||
"""
|
||||
return diff_to_180_180(self.rotation - target)
|
||||
|
||||
def is_rotation_near(self, target, threshold=10):
|
||||
return abs(self.rotation_diff(target)) <= threshold
|
||||
|
||||
|
||||
def diff_to_180_180(diff):
|
||||
"""
|
||||
Args:
|
||||
diff: Degree diff
|
||||
|
||||
Returns:
|
||||
float: Degree diff (-180~180)
|
||||
"""
|
||||
diff = diff % 360
|
||||
if diff > 180:
|
||||
diff -= 360
|
||||
return round(diff, 3)
|
||||
|
||||
|
||||
def diff_to_0_360(diff):
|
||||
"""
|
||||
Args:
|
||||
diff: Degree diff
|
||||
|
||||
Returns:
|
||||
float: Degree diff (0~360)
|
||||
"""
|
||||
return round(diff % 360, 3)
|
||||
|
Loading…
Reference in New Issue
Block a user