Merge pull request #3125 from WLLEGit/dev

feat: MaaCore添加肉鸽助战支持
This commit is contained in:
MistEO
2022-12-20 14:09:10 +08:00
committed by GitHub
21 changed files with 519 additions and 10 deletions

View File

@@ -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 // 是否可以是非好友助战干员,可选,默认 falseuse_support为true时有效
}
```

View File

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

View File

@@ -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 の場合に有効です。
}
```

View File

@@ -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 // 是否可以是非好友助戰幹員,可選,預設 falseuse_support為true時有效
}
```

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,8 @@ namespace asst
Squad, // 分队类型, like 指挥分队, 矛头分队, etc
Roles, // 职业类型, like 先手必胜, 稳扎稳打, etc
CoreChar, // 首选干员, 干员名
UseSupport, // 使用助战
UseNonfriendSupport // 可以使用非好友助战
// CoCoreChar, // 次选干员, 干员名
};

View File

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

View File

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

View 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);
}

View File

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