mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-16 01:40:46 +08:00
@@ -200,7 +200,9 @@ AsstTaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const cha
|
||||
// 投资满了自动停止任务,可选,默认 false
|
||||
"squad": string, // 开局分队,可选,例如 "突击战术分队" 等,默认 "指挥分队"
|
||||
"roles": string, // 开局职业组,可选,例如 "先手必胜" 等,默认 "取长补短"
|
||||
"core_char": string // 开局干员名,可选,仅支持单个干员中!文!名!。默认识别练度自动选择
|
||||
"core_char": string, // 开局干员名,可选,仅支持单个干员中!文!名!。默认识别练度自动选择
|
||||
"use_support": bool, // 开局干员是否为助战干员,可选,默认 false
|
||||
"use_nonfriend_support": bool // 是否可以是非好友助战干员,可选,默认 false,use_support为true时有效,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -188,7 +188,9 @@ Appends a task.
|
||||
// Stop the task when investment is full, optional, by default false
|
||||
"squad": string, // Squad name like "assault squad", optional, by default "Default Squad"
|
||||
"roles": string, // Roles, optional
|
||||
"core_char": string // Operator name, optional. Will recognize auto-selection of levels.
|
||||
"core_char": string, // Operator name, optional. Will recognize auto-selection of levels.
|
||||
"use_support": bool, // Whether "core_char" is a support unit, optional, by default false
|
||||
"use_nonfriend_support": bool // Whether support unit can be non-friend support unit, optional, by default false, valid when use_support=true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -187,7 +187,9 @@ TaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const char* p
|
||||
// 投資額が一杯になったら動作停止, オプション, デフォルトは false
|
||||
"squad": string, // "突撃戦術分隊" などの分隊指定, オプション, デフォルトは "デフォルト"
|
||||
"roles": string, // 召集組合, オプション
|
||||
"core_char": string // オペレーター名, オプション. レベルに応じて自動で選択します.
|
||||
"core_char": string, // オペレーター名, オプション. レベルに応じて自動で選択します.
|
||||
"use_support": bool, // 開始役員("core_char")がアシストであるかどうかは、オプション、デフォルトは false
|
||||
"use_nonfriend_support": bool // 非フレンドのアシスタントであるかどうかを、オプション、デフォルトの false、use_supportが true の場合に有効です。
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -187,7 +187,9 @@ TaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const char* p
|
||||
// 投資滿了自動停止任務,可選,預設 false
|
||||
"squad": string, // 開局分隊,可選,例如 "突擊戰術分隊" 等,預設 "指揮分隊"
|
||||
"roles": string, // 開局職業組,可選,例如 "先手必勝" 等,預設 "取長補短"
|
||||
"core_char": string // 開局幹員名,可選,僅支援單個幹員中!文!名! 。預設辨識練度自動選擇
|
||||
"core_char": string, // 開局幹員名,可選,僅支援單個幹員中!文!名! 。預設辨識練度自動選擇
|
||||
"use_support": bool, // 開局幹員是否為助戰幹員,可選,預設 false
|
||||
"use_nonfriend_support": bool // 是否可以是非好友助戰幹員,可選,預設 false,use_support為true時有效
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -8071,7 +8071,8 @@
|
||||
"Phantom@Roguelike@ChooseDifficulty": {},
|
||||
"Phantom@Roguelike@ChooseOper": {
|
||||
"next": [
|
||||
"Phantom@Roguelike@ChooseOperConfirm"
|
||||
"Phantom@Roguelike@ChooseOperConfirm",
|
||||
"Phantom@Roguelike@RecruitSkip"
|
||||
]
|
||||
},
|
||||
"Phantom@Roguelike@CloseCollection": {
|
||||
@@ -8257,7 +8258,8 @@
|
||||
"next": [
|
||||
"Mizuki@Roguelike@CloseCollectionContinue",
|
||||
"Mizuki@Roguelike@CloseCollectionClose",
|
||||
"Mizuki@Roguelike@ChooseOperConfirm"
|
||||
"Mizuki@Roguelike@ChooseOperConfirm",
|
||||
"Mizuki@Roguelike@RecruitSkip"
|
||||
]
|
||||
},
|
||||
"Mizuki@Roguelike@CloseCollection": {
|
||||
@@ -9135,5 +9137,122 @@
|
||||
"next": [
|
||||
"RoguelikeWaitBattleStart"
|
||||
]
|
||||
},
|
||||
"RoguelikeRecruitSupportOcr": {
|
||||
"algorithm": "OcrDetect",
|
||||
"doc": "肉鸽招募助战时干员姓名位置,仅使用roi域",
|
||||
"text": [],
|
||||
"roi": [
|
||||
120,
|
||||
565,
|
||||
1160,
|
||||
35
|
||||
]
|
||||
},
|
||||
"RoguelikeRecruitSupportOff1": {
|
||||
"template": "empty.png",
|
||||
"rectMove_Doc": "肉鸽招募助战时,干员名下方颜色区域相对名字中心的偏移",
|
||||
"rectMove": [
|
||||
-64,
|
||||
54,
|
||||
45,
|
||||
33
|
||||
],
|
||||
"specialParams_Doc":"判定是否为好友的r通道阈值",
|
||||
"specialParams": [
|
||||
70
|
||||
]
|
||||
},
|
||||
"RoguelikeRecruitSupportEliteOff": {
|
||||
"template": "empty.png",
|
||||
"rectMove_Doc": "肉鸽招募助战时,精英化状态相对名字中心的偏移",
|
||||
"rectMove": [
|
||||
-26,
|
||||
-133,
|
||||
50,
|
||||
41
|
||||
],
|
||||
"specialParams_Doc":"二值化的阈值",
|
||||
"specialParams": [
|
||||
250
|
||||
]
|
||||
},
|
||||
"RoguelikeRecruitSupportLevelOff": {
|
||||
"template": "empty.png",
|
||||
"rectMove_Doc": "肉鸽招募助战时,等级相对名字中心的偏移",
|
||||
"rectMove": [
|
||||
18,
|
||||
-81,
|
||||
33,
|
||||
21
|
||||
]
|
||||
},
|
||||
"RoguelikeRefreshSupportBtnOcr":{
|
||||
"algorithm": "OcrDetect",
|
||||
"text":["更新助战列表"],
|
||||
"roi":[
|
||||
1097,
|
||||
13,
|
||||
164,
|
||||
38
|
||||
],
|
||||
"postDelay": 3000,
|
||||
"postDelay_Doc": "点击刷新后的等待时间"
|
||||
},
|
||||
"RoguelikeChooseSupportBtnOcr":{
|
||||
"algorithm": "OcrDetect",
|
||||
"text":["选择助战"],
|
||||
"roi":[
|
||||
1011,
|
||||
9,
|
||||
177,
|
||||
47
|
||||
]
|
||||
},
|
||||
"RoguelikeSupportSwipeRight":{
|
||||
"baseTask": "SwipeToTheRight",
|
||||
"postDelay": 1000
|
||||
},
|
||||
"RoguelikeSupportSwipeLeft":{
|
||||
"baseTask": "SwipeToTheLeft",
|
||||
"postDelay": 1000
|
||||
},
|
||||
"Roguelike@RecruitSupportConfirm": {
|
||||
"algorithm": "OcrDetect",
|
||||
"action": "ClickSelf",
|
||||
"text": [
|
||||
"招募助战"
|
||||
],
|
||||
"roi": [
|
||||
770,
|
||||
500,
|
||||
160,
|
||||
40
|
||||
],
|
||||
"preDelay": 200
|
||||
},
|
||||
"RoguelikeRecruitSupportElite": {
|
||||
"Doc": "base_task",
|
||||
"template": "empty.png",
|
||||
"templThreshold": 0.6
|
||||
},
|
||||
"RoguelikeRecruitSupportElite0": {
|
||||
"baseTask": "RoguelikeRecruitSupportElite"
|
||||
},
|
||||
"RoguelikeRecruitSupportElite1": {
|
||||
"baseTask": "RoguelikeRecruitSupportElite"
|
||||
},
|
||||
"RoguelikeRecruitSupportElite2": {
|
||||
"baseTask": "RoguelikeRecruitSupportElite"
|
||||
},
|
||||
"RoguelikeRecruitSupportEnterFlag":{
|
||||
"template": "Roguelike@RefreshSupportBtn.png",
|
||||
"action": "DoNothing",
|
||||
"roi": [
|
||||
1094,
|
||||
10,
|
||||
172,
|
||||
47
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
BIN
resource/template/Roguelike@RefreshSupportBtn.png
Normal file
BIN
resource/template/Roguelike@RefreshSupportBtn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
BIN
resource/template/RoguelikeRecruitSupportElite0.png
Normal file
BIN
resource/template/RoguelikeRecruitSupportElite0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
resource/template/RoguelikeRecruitSupportElite1.png
Normal file
BIN
resource/template/RoguelikeRecruitSupportElite1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
resource/template/RoguelikeRecruitSupportElite2.png
Normal file
BIN
resource/template/RoguelikeRecruitSupportElite2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -221,6 +221,28 @@ namespace asst
|
||||
int level = 0;
|
||||
};
|
||||
|
||||
enum class RoguelikeSupportAnalyzeMode
|
||||
{
|
||||
ChooseSupportBtn,
|
||||
AnalyzeChars,
|
||||
RefreshSupportBtn
|
||||
};
|
||||
|
||||
struct RoguelikeRecruitSupportCharInfo
|
||||
{
|
||||
BattleRecruitOperInfo oper_info;
|
||||
bool is_friend = false; // 是否为好友助战
|
||||
int max_elite = 0; // 两次招募后的实际精英化与等级
|
||||
int max_level = 0;
|
||||
};
|
||||
|
||||
struct RoguelikeRefreshSupportInfo
|
||||
{
|
||||
Rect rect;
|
||||
bool in_cooldown = false;
|
||||
int remain_secs = 0; // 刷新冷却时间
|
||||
};
|
||||
|
||||
using BattleAttackRange = std::vector<Point>;
|
||||
|
||||
static constexpr std::string_view RoguelikePhantomThemeName = "Phantom";
|
||||
|
||||
@@ -43,6 +43,9 @@ bool asst::BattleDataConfig::parse(const json::value& json)
|
||||
data.location_type = iter->second;
|
||||
}
|
||||
|
||||
const auto& rarity = char_data_json.at("rarity").as_integer();
|
||||
data.rarity = rarity;
|
||||
|
||||
m_chars.emplace(std::move(name), std::move(data));
|
||||
}
|
||||
for (const auto& [id, points_json] : json.at("ranges").as_object()) {
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<ClInclude Include="Vision\OcrWithPreprocessImageAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeFormationImageAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeRecruitImageAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeRecruitSupportAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeSkillSelectionImageAnalyzer.h" />
|
||||
<ClInclude Include="Config\AbstractConfig.h" />
|
||||
<ClInclude Include="Config\AbstractConfigWithTempl.h" />
|
||||
@@ -162,6 +163,7 @@
|
||||
<ClCompile Include="Vision\OcrWithPreprocessImageAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeFormationImageAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeRecruitImageAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeRecruitSupportAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeSkillSelectionImageAnalyzer.cpp" />
|
||||
<ClCompile Include="Config\AbstractConfig.cpp" />
|
||||
<ClCompile Include="Config\GeneralConfig.cpp" />
|
||||
|
||||
@@ -153,6 +153,9 @@
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeSkillSelectionImageAnalyzer.h">
|
||||
<Filter>源文件\Vision\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeRecruitSupportAnalyzer.h">
|
||||
<Filter>源文件\Vision\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Config\AbstractConfig.h">
|
||||
<Filter>源文件\Config</Filter>
|
||||
</ClInclude>
|
||||
@@ -491,6 +494,9 @@
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeSkillSelectionImageAnalyzer.cpp">
|
||||
<Filter>源文件\Vision\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeRecruitSupportAnalyzer.cpp">
|
||||
<Filter>源文件\Vision\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Config\AbstractConfig.cpp">
|
||||
<Filter>源文件\Config</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace asst
|
||||
static inline const std::string RoguelikeCharRarityPrefix = "RoguelikeRarity-";
|
||||
static inline const std::string RoguelikeCharOverview = "RoguelikeOverview";
|
||||
static inline const std::string RoguelikeCoreChar = "RoguelikeCoreChar";
|
||||
static inline const std::string RoguelikeUseSupport = "RoguelikeUseSupport";
|
||||
static inline const std::string RoguelikeUseNonfriendSupport = "RoguelikeUseNonfriendSupport";
|
||||
static inline const std::string RoguelikeTraderNoLongerBuy = "RoguelikeNoLongerBuy";
|
||||
static inline const std::string RoguelikeSkillUsagePrefix = "RoguelikeSkillUsage-";
|
||||
static inline const std::string RoguelikeTeamFullWithoutRookie = "RoguelikeTeamFullWithoutRookie";
|
||||
|
||||
@@ -104,6 +104,12 @@ bool asst::RoguelikeTask::set_params(const json::value& params)
|
||||
if (auto core_char_opt = params.find<std::string>("core_char"); core_char_opt && !core_char_opt->empty()) {
|
||||
m_custom_start_task_ptr->set_custom(RoguelikeCustomType::CoreChar, *core_char_opt);
|
||||
}
|
||||
if (auto use_support_opt = params.find<bool>("use_support"); use_support_opt) {
|
||||
m_custom_start_task_ptr->set_custom(RoguelikeCustomType::UseSupport, *use_support_opt ? "1" : "0");
|
||||
}
|
||||
if (auto use_nonfriend_opt = params.find<bool>("use_nonfriend_support"); use_nonfriend_opt) {
|
||||
m_custom_start_task_ptr->set_custom(RoguelikeCustomType::UseNonfriendSupport, *use_nonfriend_opt ? "1" : "0");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -141,6 +141,8 @@ bool asst::RoguelikeCustomStartTaskPlugin::hijack_core_char()
|
||||
|
||||
sleep(Task.get("RoguelikeCustom-HijackCoChar")->pre_delay);
|
||||
|
||||
status()->set_str(Status::RoguelikeUseSupport, m_customs[RoguelikeCustomType::UseSupport]);
|
||||
status()->set_str(Status::RoguelikeUseNonfriendSupport, m_customs[RoguelikeCustomType::UseNonfriendSupport]);
|
||||
status()->set_str(Status::RoguelikeCoreChar, char_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace asst
|
||||
Squad, // 分队类型, like 指挥分队, 矛头分队, etc
|
||||
Roles, // 职业类型, like 先手必胜, 稳扎稳打, etc
|
||||
CoreChar, // 首选干员, 干员名
|
||||
UseSupport, // 使用助战
|
||||
UseNonfriendSupport // 可以使用非好友助战
|
||||
// CoCoreChar, // 次选干员, 干员名
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#include "RoguelikeRecruitTaskPlugin.h"
|
||||
|
||||
#include "Controller.h"
|
||||
#include "Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h"
|
||||
#include "Config/Miscellaneous/BattleDataConfig.h"
|
||||
#include "Config/Roguelike/RoguelikeRecruitConfig.h"
|
||||
#include "Config/TaskData.h"
|
||||
#include "Controller.h"
|
||||
#include "Status.h"
|
||||
#include "Task/ProcessTask.h"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/NoWarningCV.h"
|
||||
#include "Vision/OcrImageAnalyzer.h"
|
||||
#include "Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h"
|
||||
#include "Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h"
|
||||
|
||||
bool asst::RoguelikeRecruitTaskPlugin::verify(AsstMsg msg, const json::value& details) const
|
||||
{
|
||||
@@ -51,8 +54,17 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (check_core_char()) {
|
||||
return true;
|
||||
// 开局干员
|
||||
bool use_support = get_status_bool(Status::RoguelikeUseSupport);
|
||||
if (use_support) {
|
||||
if (check_support_char()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (check_core_char()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool recruited = false;
|
||||
@@ -370,6 +382,93 @@ bool asst::RoguelikeRecruitTaskPlugin::check_char(const std::string& char_name,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool asst::RoguelikeRecruitTaskPlugin::check_support_char()
|
||||
{
|
||||
LogTraceFunction;
|
||||
const int MaxRefreshTimes = 3;
|
||||
|
||||
auto core_opt = status()->get_str(Status::RoguelikeCoreChar);
|
||||
status()->set_str(Status::RoguelikeCoreChar, "");
|
||||
if (core_opt && !core_opt->empty()) {
|
||||
if (check_support_char(core_opt.value(), MaxRefreshTimes)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& name, const int max_refresh)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
// 判断是否存在“选择助战”按钮,存在则点击
|
||||
auto screen_choose = ctrler()->get_image();
|
||||
RoguelikeRecruitSupportAnalyzer analyzer_choose(screen_choose);
|
||||
analyzer_choose.set_mode(RoguelikeSupportAnalyzeMode::ChooseSupportBtn);
|
||||
if (!analyzer_choose.analyze()) { // 非开局招募,无助战按钮
|
||||
return false;
|
||||
}
|
||||
const auto& choose_btn_rect = analyzer_choose.get_result_choose_support();
|
||||
Log.info(__FUNCTION__, "| check choose support btn ", choose_btn_rect);
|
||||
ctrler()->click(choose_btn_rect);
|
||||
ProcessTask(*this, { "RoguelikeRecruitSupportEnterFlag" }).run(); // 等待页面加载
|
||||
|
||||
|
||||
// 识别所有干员,应该最多两页
|
||||
const int SwipeTimes = 1;
|
||||
|
||||
std::vector<RoguelikeRecruitSupportCharInfo> satisfied_chars;
|
||||
for (int retry = 0; retry <= max_refresh; ++retry) {
|
||||
for (int swipe = 0; swipe < SwipeTimes; ++swipe) {
|
||||
auto screen_char = ctrler()->get_image();
|
||||
RoguelikeRecruitSupportAnalyzer analyzer_char(screen_char);
|
||||
analyzer_char.set_mode(RoguelikeSupportAnalyzeMode::AnalyzeChars);
|
||||
analyzer_char.set_required({ name });
|
||||
if (analyzer_char.analyze()) {
|
||||
auto& chars_page = analyzer_char.get_result_char();
|
||||
|
||||
bool use_nonfriend_support = get_status_bool(Status::RoguelikeUseNonfriendSupport);
|
||||
auto check_satisfiy = [&use_nonfriend_support](const RoguelikeRecruitSupportCharInfo& chara) {
|
||||
return chara.is_friend || use_nonfriend_support;
|
||||
};
|
||||
std::copy_if(chars_page.begin(), chars_page.end(),
|
||||
std::inserter(satisfied_chars, std::begin(satisfied_chars)), check_satisfiy);
|
||||
|
||||
if (satisfied_chars.size()) break;
|
||||
}
|
||||
ProcessTask(*this, { "RoguelikeSupportSwipeRight" }).run();
|
||||
}
|
||||
if (satisfied_chars.size()) break;
|
||||
|
||||
// 刷新助战
|
||||
if (retry >= max_refresh) break;
|
||||
auto screen_refresh = ctrler()->get_image();
|
||||
RoguelikeRecruitSupportAnalyzer analyzer_refresh(screen_refresh);
|
||||
analyzer_refresh.set_mode(RoguelikeSupportAnalyzeMode::RefreshSupportBtn);
|
||||
if (!analyzer_refresh.analyze()) {
|
||||
click_return_button();
|
||||
return false;
|
||||
}
|
||||
auto& refresh_info = analyzer_refresh.get_result_refresh();
|
||||
if (refresh_info.in_cooldown) sleep(refresh_info.remain_secs * 1000);
|
||||
ctrler()->click(refresh_info.rect);
|
||||
sleep(Task.get("RoguelikeRefreshSupportBtnOcr")->post_delay);
|
||||
ProcessTask(*this, { "RoguelikeSupportSwipeLeft" }).run();
|
||||
}
|
||||
if (satisfied_chars.empty()) {
|
||||
// 找不到需要的助战干员,返回正常招募逻辑
|
||||
Log.info(__FUNCTION__, "| can't find support char `", name, "`");
|
||||
click_return_button();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 点击干员并记录信息
|
||||
auto& satisfied_char = satisfied_chars.front();
|
||||
select_oper(satisfied_char.oper_info);
|
||||
|
||||
// 确认选择
|
||||
ProcessTask(*this, { "Roguelike@RecruitSupportConfirm" }).run();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asst::RoguelikeRecruitTaskPlugin::check_core_char()
|
||||
{
|
||||
LogTraceFunction;
|
||||
@@ -400,6 +499,12 @@ void asst::RoguelikeRecruitTaskPlugin::select_oper(const BattleRecruitOperInfo&
|
||||
status()->set_str(Status::RoguelikeCharOverview, overview.to_string());
|
||||
}
|
||||
|
||||
inline bool asst::RoguelikeRecruitTaskPlugin::get_status_bool(const std::string& key)
|
||||
{
|
||||
auto value_opt = status()->get_str(key);
|
||||
return value_opt && value_opt->size() == 1 && value_opt.value()[0] == '1';
|
||||
}
|
||||
|
||||
void asst::RoguelikeRecruitTaskPlugin::swipe_to_the_left_of_operlist(int loop_times)
|
||||
{
|
||||
for (int i = 0; i != loop_times; ++i) {
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace asst
|
||||
void slowly_swipe(bool to_left);
|
||||
|
||||
private:
|
||||
bool check_support_char();
|
||||
bool check_support_char(const std::string& name, const int max_refresh);
|
||||
|
||||
bool check_core_char();
|
||||
BattleRole get_oper_role(const std::string& name);
|
||||
bool is_oper_melee(const std::string& name);
|
||||
@@ -40,5 +43,7 @@ namespace asst
|
||||
// 返回值: 招募结果 (true: 成功; false: 失败)
|
||||
bool check_char(const std::string& char_name, bool is_rtl = false);
|
||||
void select_oper(const BattleRecruitOperInfo& oper);
|
||||
|
||||
inline bool get_status_bool(const std::string& key);
|
||||
};
|
||||
}
|
||||
|
||||
193
src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp
Normal file
193
src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include "RoguelikeRecruitSupportAnalyzer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
#include "Config/Miscellaneous/BattleDataConfig.h"
|
||||
#include "Config/TaskData.h"
|
||||
#include "Controller.h"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/NoWarningCV.h"
|
||||
#include "Vision/MatchImageAnalyzer.h"
|
||||
#include "Vision/OcrImageAnalyzer.h"
|
||||
#include "Vision/OcrWithFlagTemplImageAnalyzer.h"
|
||||
|
||||
bool asst::RoguelikeRecruitSupportAnalyzer::analyze()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (m_mode == RoguelikeSupportAnalyzeMode::ChooseSupportBtn) {
|
||||
// 识别“选择助战”
|
||||
OcrImageAnalyzer analyzer(m_image);
|
||||
const auto& task = Task.get<OcrTaskInfo>("RoguelikeChooseSupportBtnOcr");
|
||||
analyzer.set_roi(task->roi);
|
||||
analyzer.set_required(task->text);
|
||||
if (!analyzer.analyze()) return false;
|
||||
m_choose_support_result = analyzer.get_result().front().rect;
|
||||
Log.info(__FUNCTION__, "| ChooseSupportBtn");
|
||||
return true;
|
||||
}
|
||||
else if (m_mode == RoguelikeSupportAnalyzeMode::AnalyzeChars) {
|
||||
// 识别干员
|
||||
OcrImageAnalyzer analyzer(m_image);
|
||||
analyzer.set_roi(Task.get("RoguelikeRecruitSupportOcr")->roi);
|
||||
analyzer.set_required(m_required);
|
||||
if (!analyzer.analyze()) return false;
|
||||
|
||||
const auto& char_name_rects = analyzer.get_result();
|
||||
const auto& task_off1 = Task.get("RoguelikeRecruitSupportOff1");
|
||||
const auto& task_off_elite = Task.get("RoguelikeRecruitSupportEliteOff");
|
||||
const auto& task_off_level = Task.get("RoguelikeRecruitSupportLevelOff");
|
||||
m_char_result.clear();
|
||||
for (const auto& char_rect : char_name_rects) {
|
||||
Rect name_rect = char_rect.rect;
|
||||
name_rect.x = name_rect.x + name_rect.width / 2;
|
||||
|
||||
// 识别等级区域
|
||||
Rect level_roi = name_rect.move(task_off_level->rect_move);
|
||||
int char_level = match_level(level_roi);
|
||||
if (char_level <= 0) {
|
||||
// 等级识别失败,可能希望不足,舍弃结果
|
||||
Log.info(__FUNCTION__, "| match_level failed ", char_rect.text, char_level);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断是否为好友助战
|
||||
Rect color_roi = name_rect.move(task_off1->rect_move);
|
||||
bool is_friend = judge_is_friend(color_roi, task_off1->special_params.front());
|
||||
|
||||
// 匹配精英化状态
|
||||
Rect elite_roi = name_rect.move(task_off_elite->rect_move);
|
||||
int char_elite = match_elite(elite_roi, task_off_elite->special_params.front());
|
||||
|
||||
RoguelikeRecruitSupportCharInfo char_info {
|
||||
{ char_rect.text, char_rect.rect, char_elite, char_level }, is_friend, char_elite, char_level
|
||||
};
|
||||
|
||||
// 助战招募最多精一
|
||||
int rarity = BattleData.get_rarity(char_rect.text);
|
||||
if (rarity == 0) {
|
||||
// 非干员名,可能是屏幕边角,舍弃结果
|
||||
Log.info(__FUNCTION__, "| can't get rarity of `", char_rect.text, "`");
|
||||
continue;
|
||||
}
|
||||
else if (rarity <= 3) {
|
||||
// 无需处理
|
||||
}
|
||||
else {
|
||||
if (char_elite == 2) {
|
||||
char_info.oper_info.elite = 1;
|
||||
char_info.oper_info.level = 60 + 10 * (rarity - 4);
|
||||
}
|
||||
}
|
||||
|
||||
Log.info(__FUNCTION__, "| AnalyzeChars append ", char_info.oper_info.name, char_info.oper_info.rect,
|
||||
char_elite, char_level, is_friend, char_info.max_elite, char_info.max_level);
|
||||
m_char_result.push_back(char_info);
|
||||
}
|
||||
return !m_char_result.empty();
|
||||
}
|
||||
else if (m_mode == RoguelikeSupportAnalyzeMode::RefreshSupportBtn) {
|
||||
// 识别“更新助战列表”
|
||||
OcrImageAnalyzer analyzer(m_image);
|
||||
const auto& task = Task.get<OcrTaskInfo>("RoguelikeRefreshSupportBtnOcr");
|
||||
analyzer.set_roi(task->roi);
|
||||
if (!analyzer.analyze()) return false;
|
||||
const auto& results = analyzer.get_result();
|
||||
for (const auto& result : results) {
|
||||
Log.info(__FUNCTION__, "| RefreshSupportBtn parse `", result.text, "`", result.score);
|
||||
if (std::count(task->text.begin(), task->text.end(), result.text)) {
|
||||
m_refresh_result = { result.rect, false, 0 };
|
||||
return true;
|
||||
}
|
||||
else if (std::regex_match(result.text, std::regex("[0-9]{2}:[0-9]{2}:[0-9]{2}"))) {
|
||||
const auto& hour = std::atoi(result.text.substr(2).c_str());
|
||||
const auto& min = std::atoi(result.text.substr(3, 2).c_str());
|
||||
const auto& sec = std::atoi(result.text.substr(7, 2).c_str());
|
||||
m_refresh_result = { result.rect, true, 3600 * hour + 60 * min + sec };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int asst::RoguelikeRecruitSupportAnalyzer::match_elite(const Rect& roi, const int threshold)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
static const std::unordered_map<std::string, int> EliteTaskName = {
|
||||
{ "RoguelikeRecruitSupportElite0", 0 },
|
||||
{ "RoguelikeRecruitSupportElite1", 1 },
|
||||
{ "RoguelikeRecruitSupportElite2", 2 },
|
||||
};
|
||||
|
||||
int elite_result = 0;
|
||||
double max_score = 0;
|
||||
|
||||
// 二值化,避免干员立绘影响识别,精英化标识主体颜色是纯白色
|
||||
cv::Mat bin_img;
|
||||
cv::threshold(m_image, bin_img, threshold, 255, cv::THRESH_BINARY);
|
||||
|
||||
for (const auto& [task_name, elite] : EliteTaskName) {
|
||||
MatchImageAnalyzer analyzer(bin_img);
|
||||
auto task_ptr = Task.get(task_name);
|
||||
analyzer.set_task_info(task_ptr);
|
||||
analyzer.set_roi(roi);
|
||||
analyzer.set_threshold(Task.get<MatchTaskInfo>(task_name)->templ_threshold);
|
||||
|
||||
if (!analyzer.analyze()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double score = analyzer.get_result().score;
|
||||
if (score > max_score) {
|
||||
max_score = score;
|
||||
elite_result = elite;
|
||||
}
|
||||
}
|
||||
|
||||
Log.info(__FUNCTION__, "| ", roi, elite_result, max_score);
|
||||
return elite_result;
|
||||
}
|
||||
|
||||
int asst::RoguelikeRecruitSupportAnalyzer::judge_is_friend(const Rect& roi, const double threshold)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
const auto& color_img = m_image(make_rect<cv::Rect>(roi)); // 截取颜色部分
|
||||
|
||||
// 用red通道判断是橙色还是蓝色,橙色为好友助战
|
||||
std::vector<cv::Mat> channels;
|
||||
split(color_img, channels);
|
||||
const auto& red_channel = channels.at(2);
|
||||
auto r_mean = cv::mean(red_channel)[0];
|
||||
bool is_friend = (r_mean > threshold);
|
||||
|
||||
Log.info(__FUNCTION__, "| ", roi, r_mean, threshold);
|
||||
|
||||
return is_friend;
|
||||
}
|
||||
|
||||
int asst::RoguelikeRecruitSupportAnalyzer::match_level(const Rect& roi)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
OcrWithPreprocessImageAnalyzer analyzer(m_image);
|
||||
analyzer.set_task_info("NumberOcrReplace");
|
||||
analyzer.set_roi(roi);
|
||||
analyzer.set_expansion(1);
|
||||
|
||||
if (!analyzer.analyze()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Log.info(__FUNCTION__, "| ", roi, "`", analyzer.get_result().front().text, "`");
|
||||
const std::string& level = analyzer.get_result().front().text;
|
||||
if (level.empty() || !ranges::all_of(level, [](char c) -> bool { return std::isdigit(c); })) {
|
||||
return 0;
|
||||
}
|
||||
return std::stoi(level);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "Vision/AbstractImageAnalyzer.h"
|
||||
|
||||
#include "Common/AsstBattleDef.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class RoguelikeRecruitSupportAnalyzer final : public AbstractImageAnalyzer
|
||||
{
|
||||
public:
|
||||
using AbstractImageAnalyzer::AbstractImageAnalyzer;
|
||||
virtual ~RoguelikeRecruitSupportAnalyzer() noexcept override = default;
|
||||
|
||||
bool analyze() override;
|
||||
|
||||
void set_mode(RoguelikeSupportAnalyzeMode mode) noexcept { m_mode = mode; }
|
||||
void set_required(std::vector<std::string> required) noexcept { m_required = required; }
|
||||
|
||||
const auto& get_result_choose_support() { return m_choose_support_result; }
|
||||
const auto& get_result_char() { return m_char_result; }
|
||||
const auto& get_result_refresh() { return m_refresh_result; }
|
||||
|
||||
private:
|
||||
RoguelikeSupportAnalyzeMode m_mode;
|
||||
Rect m_choose_support_result;
|
||||
RoguelikeRefreshSupportInfo m_refresh_result;
|
||||
std::vector<RoguelikeRecruitSupportCharInfo> m_char_result;
|
||||
std::vector<std::string> m_required;
|
||||
|
||||
int judge_is_friend(const Rect& roi, const double r_threshold);
|
||||
int match_elite(const Rect& roi, const int threshold);
|
||||
int match_level(const Rect& roi);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user