mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-18 10:10:45 +08:00
feat: 自动编队自动借助战
This commit is contained in:
@@ -3503,6 +3503,37 @@
|
||||
["^资深.+", "资深干员"]
|
||||
]
|
||||
},
|
||||
"SelectSupportEnter": {
|
||||
"algorithm": "JustReturn",
|
||||
"next": ["SelectSupportStartSelect", "SelectSupportBackToFormationPage"]
|
||||
},
|
||||
"SelectSupportBackToFormationPage": {
|
||||
"action": "ClickSelf",
|
||||
"roi": [0, 0, 144, 142],
|
||||
"next": ["SelectSupportEnter"]
|
||||
},
|
||||
"SelectSupportStartSelect": {
|
||||
"action": "ClickSelf",
|
||||
"roi": [982, 228, 126, 176]
|
||||
},
|
||||
"SelectSupportOperSkill0": {
|
||||
"algorithm": "JustReturn"
|
||||
},
|
||||
"SelectSupportOperSkill1": {
|
||||
"template": "SelectSupportOperSkill.png",
|
||||
"action": "ClickSelf",
|
||||
"roi": [781, 320, 66, 38]
|
||||
},
|
||||
"SelectSupportOperSkill2": {
|
||||
"template": "SelectSupportOperSkill.png",
|
||||
"action": "ClickSelf",
|
||||
"roi": [900, 320, 64, 38]
|
||||
},
|
||||
"SelectSupportOperSkill3": {
|
||||
"template": "SelectSupportOperSkill.png",
|
||||
"action": "ClickSelf",
|
||||
"roi": [1010, 320, 70, 38]
|
||||
},
|
||||
"RecruitRefresh": {
|
||||
"action": "ClickSelf",
|
||||
"roi": [850, 340, 220, 170],
|
||||
@@ -4939,6 +4970,43 @@
|
||||
"BattleOperRoleTank": {},
|
||||
"BattleOperRoleWarrior": {},
|
||||
"BattleOperRoleDrone": {},
|
||||
"SupportOperRoleBase": {
|
||||
"algorithm": "JustReturn",
|
||||
"action": "ClickRect",
|
||||
"postDelay": 1000
|
||||
},
|
||||
"SupportOperRoleCaster": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 406, 55, 50]
|
||||
},
|
||||
"SupportOperRoleMedic": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 483, 55, 50]
|
||||
},
|
||||
"SupportOperRolePioneer": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 102, 55, 50]
|
||||
},
|
||||
"SupportOperRoleSniper": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 330, 55, 50]
|
||||
},
|
||||
"SupportOperRoleSpecial": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 637, 55, 50]
|
||||
},
|
||||
"SupportOperRoleSupport": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 556, 55, 50]
|
||||
},
|
||||
"SupportOperRoleTank": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 252, 55, 50]
|
||||
},
|
||||
"SupportOperRoleWarrior": {
|
||||
"baseTask": "SupportOperRoleBase",
|
||||
"specificRect": [30, 174, 55, 50]
|
||||
},
|
||||
"BattleOperCost": {
|
||||
"template": "empty.png",
|
||||
"rectMove": [-10, 12, 30, 17]
|
||||
|
||||
BIN
resource/template/SelectSupportBackToFormationPage.png
Normal file
BIN
resource/template/SelectSupportBackToFormationPage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 689 B |
BIN
resource/template/SelectSupportOperSkill.png
Normal file
BIN
resource/template/SelectSupportOperSkill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 747 B |
BIN
resource/template/SelectSupportStartSelect.png
Normal file
BIN
resource/template/SelectSupportStartSelect.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -341,9 +341,20 @@ struct RefreshSupportInfo
|
||||
|
||||
namespace asst
|
||||
{
|
||||
inline std::string enum_to_string(asst::battle::Role role)
|
||||
inline std::string enum_to_string(asst::battle::Role role, bool en = false)
|
||||
{
|
||||
using asst::battle::Role;
|
||||
if (en) {
|
||||
static const std::unordered_map<Role, std::string> RoleToName_EN = {
|
||||
{ Role::Warrior, "Warrior" }, { Role::Pioneer, "Pioneer" }, { Role::Medic, "Medic" },
|
||||
{ Role::Tank, "Tank" }, { Role::Sniper, "Sniper" }, { Role::Caster, "Caster" },
|
||||
{ Role::Support, "Support" }, { Role::Special, "Special" }, { Role::Drone, "Drone" },
|
||||
};
|
||||
if (auto iter = RoleToName_EN.find(role); iter != RoleToName_EN.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
static const std::unordered_map<Role, std::string> RoleToName = {
|
||||
{ Role::Warrior, "近卫" }, { Role::Pioneer, "先锋" }, { Role::Medic, "医疗" },
|
||||
{ Role::Tank, "重装" }, { Role::Sniper, "狙击" }, { Role::Caster, "术师" },
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
<ClInclude Include="Utils\Algorithm.hpp" />
|
||||
<ClInclude Include="Utils\File.hpp" />
|
||||
<ClInclude Include="Utils\LibraryHolder.hpp" />
|
||||
<ClInclude Include="Vision\Battle\BattleRecruitSupportAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeParameterAnalyzer.h" />
|
||||
<ClInclude Include="Vision\VisionHelper.h" />
|
||||
<ClInclude Include="Vision\Battle\BattleFormationAnalyzer.h" />
|
||||
@@ -302,6 +303,7 @@
|
||||
<ClCompile Include="Task\SSS\SSSBattleProcessTask.cpp" />
|
||||
<ClCompile Include="Task\SSS\SSSDropRewardsTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\SSS\SSSStageManagerTask.cpp" />
|
||||
<ClCompile Include="Vision\Battle\BattleRecruitSupportAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeParameterAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\VisionHelper.cpp" />
|
||||
<ClCompile Include="Vision\Battle\BattleFormationAnalyzer.cpp" />
|
||||
|
||||
@@ -731,6 +731,9 @@
|
||||
<ClInclude Include="LDExtras.h">
|
||||
<Filter>Source\Controller</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Vision\Battle\BattleRecruitSupportAnalyzer.h">
|
||||
<Filter>Source\Vision\Battle</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Vision\VisionHelper.cpp">
|
||||
@@ -1227,5 +1230,8 @@
|
||||
<ClCompile Include="LDExtras.cpp">
|
||||
<Filter>Source\Controller</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Vision\Battle\BattleRecruitSupportAnalyzer.cpp">
|
||||
<Filter>Source\Vision\Battle</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Utils/ImageIo.hpp"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Vision/MultiMatcher.h"
|
||||
#include "Vision/Battle/BattleRecruitSupportAnalyzer.h"
|
||||
|
||||
void asst::BattleFormationTask::append_additional_formation(AdditionalFormation formation)
|
||||
{
|
||||
@@ -69,10 +70,15 @@ bool asst::BattleFormationTask::_run()
|
||||
}
|
||||
|
||||
if (!missing_operators.empty()) {
|
||||
bool support = false;
|
||||
if (missing_operators.size() == 1) {
|
||||
// TODO: 自动借助战?
|
||||
// 记得加上 SupportUnitUsage 的壳
|
||||
support = select_support_operator(
|
||||
missing_operators.front().first,
|
||||
missing_operators.front().second.front().skill);
|
||||
}
|
||||
|
||||
if (support) return true;
|
||||
|
||||
report_missing_operators(missing_operators);
|
||||
|
||||
return false;
|
||||
@@ -115,10 +121,94 @@ bool asst::BattleFormationTask::_run()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asst::BattleFormationTask::add_formation(
|
||||
battle::Role role,
|
||||
std::vector<OperGroup> oper_group,
|
||||
std::vector<OperGroup>& missing)
|
||||
bool asst::BattleFormationTask::select_support_operator(const std::string name, int skill)
|
||||
{
|
||||
ProcessTask(*this, { "SelectSupportEnter" }).run();
|
||||
ProcessTask(*this, { "SupportOperRole" + enum_to_string(BattleData.get_role(name), true) }).run();
|
||||
|
||||
// 识别所有干员,应该最多两页
|
||||
const int MaxPageCnt = 2;
|
||||
const int max_refresh = 10;
|
||||
|
||||
std::vector<battle::roguelike::RecruitSupportCharInfo> satisfied_chars;
|
||||
for (int retry = 0; retry <= max_refresh; ++retry) {
|
||||
if (need_exit()) return false;
|
||||
for (int page = 0; page < MaxPageCnt; ++page) {
|
||||
auto screen_char = ctrler()->get_image();
|
||||
BattleRecruitSupportAnalyzer analyzer_char(screen_char);
|
||||
analyzer_char.set_mode(battle::roguelike::SupportAnalyzeMode::AnalyzeChars);
|
||||
analyzer_char.set_required({ name });
|
||||
if (analyzer_char.analyze()) {
|
||||
auto& chars_page = analyzer_char.get_result_char();
|
||||
|
||||
bool use_nonfriend_support = true;
|
||||
auto check_satisfy = [&use_nonfriend_support](const battle::roguelike::RecruitSupportCharInfo& 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_satisfy);
|
||||
|
||||
if (satisfied_chars.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (page != MaxPageCnt - 1) {
|
||||
ProcessTask(*this, { "RoguelikeSupportSwipeRight" }).run();
|
||||
}
|
||||
}
|
||||
if (satisfied_chars.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 刷新助战
|
||||
if (retry >= max_refresh) {
|
||||
break;
|
||||
}
|
||||
auto screen_refresh = ctrler()->get_image();
|
||||
BattleRecruitSupportAnalyzer analyzer_refresh(screen_refresh);
|
||||
analyzer_refresh.set_mode(battle::roguelike::SupportAnalyzeMode::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();
|
||||
|
||||
// 选择干员
|
||||
const battle::roguelike::Recruitment& oper = satisfied_char.oper_info;
|
||||
|
||||
Log.info(__FUNCTION__, "| Choose support oper:", oper.name, "( elite", oper.elite, "level", oper.level, ")");
|
||||
|
||||
ctrler()->click(oper.rect);
|
||||
|
||||
// 选择技能
|
||||
ProcessTask(*this, { "SelectSupportOperSkill" + std::to_string(skill) }).run();
|
||||
|
||||
// 确认选择
|
||||
ProcessTask(*this, { "RecruitSupportConfirm" }).run();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool asst::BattleFormationTask::add_formation(battle::Role role, std::vector<OperGroup> oper_group, std::vector<OperGroup>& missing)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
|
||||
@@ -15,9 +15,80 @@ public:
|
||||
|
||||
enum class Filter
|
||||
{
|
||||
None,
|
||||
Trust,
|
||||
Cost,
|
||||
public:
|
||||
using AbstractTask::AbstractTask;
|
||||
virtual ~BattleFormationTask() override = default;
|
||||
|
||||
enum class Filter
|
||||
{
|
||||
None,
|
||||
Trust,
|
||||
Cost,
|
||||
};
|
||||
struct AdditionalFormation
|
||||
{
|
||||
Filter filter = Filter::None;
|
||||
bool double_click_filter = true;
|
||||
battle::RoleCounts role_counts;
|
||||
};
|
||||
void append_additional_formation(AdditionalFormation formation);
|
||||
|
||||
void set_support_unit_name(std::string name);
|
||||
// 是否追加自定干员
|
||||
void set_add_user_additional(bool add_user_additional);
|
||||
// 设置追加自定干员列表
|
||||
void set_user_additional(std::vector<std::pair<std::string, int>> user_additional);
|
||||
// 是否追加低信赖干员
|
||||
void set_add_trust(bool add_trust);
|
||||
// 设置对指定编队自动编队
|
||||
void set_select_formation(int index);
|
||||
std::shared_ptr<std::unordered_map<std::string, std::string>> get_opers_in_formation() const;
|
||||
|
||||
enum class DataResource
|
||||
{
|
||||
Copilot,
|
||||
SSSCopilot,
|
||||
};
|
||||
void set_data_resource(DataResource resource);
|
||||
|
||||
protected:
|
||||
using OperGroup = std::pair<std::string, std::vector<asst::battle::OperUsage>>;
|
||||
|
||||
virtual bool _run() override;
|
||||
bool add_formation(battle::Role role, std::vector<OperGroup> oper_group, std::vector<OperGroup>& missing);
|
||||
// 追加附加干员(按部署费用等小分类)
|
||||
bool add_additional();
|
||||
// 补充刷信赖的干员,从最小的开始
|
||||
bool add_trust_operators();
|
||||
bool enter_selection_page();
|
||||
bool select_opers_in_cur_page(std::vector<OperGroup>& groups);
|
||||
void swipe_page();
|
||||
void swipe_to_the_left(int times = 2);
|
||||
bool confirm_selection();
|
||||
bool click_role_table(battle::Role role);
|
||||
bool parse_formation();
|
||||
bool select_formation(int select_index);
|
||||
bool select_random_support_unit();
|
||||
void report_missing_operators(std::vector<OperGroup>& groups);
|
||||
bool select_support_operator(const std::string name, int skill = 0);
|
||||
|
||||
std::vector<asst::TemplDetOCRer::Result> analyzer_opers();
|
||||
|
||||
std::string m_stage_name;
|
||||
std::unordered_map<battle::Role, std::vector<OperGroup>> m_formation;
|
||||
std::vector<OperGroup> m_user_formation;
|
||||
int m_size_of_operators_in_formation = 0; // 编队中干员个数
|
||||
std::shared_ptr<std::unordered_map<std::string, std::string>> m_opers_in_formation =
|
||||
std::make_shared<std::unordered_map<std::string, std::string>>(); // 编队中的干员名称-所属组名
|
||||
bool m_add_trust = false; // 是否需要追加信赖干员
|
||||
bool m_add_user_additional = false; // 补用户自定义干员
|
||||
std::vector<std::pair<std::string, int>> m_user_additional; // 追加干员表,从头往后加
|
||||
std::string m_support_unit_name;
|
||||
DataResource m_data_resource = DataResource::Copilot;
|
||||
std::vector<AdditionalFormation> m_additional;
|
||||
std::string m_last_oper_name;
|
||||
int m_select_formation_index = 0;
|
||||
int m_missing_retry_times = 1; // 识别不到干员的重试次数
|
||||
};
|
||||
|
||||
struct AdditionalFormation
|
||||
|
||||
223
src/MaaCore/Vision/Battle/BattleRecruitSupportAnalyzer.cpp
Normal file
223
src/MaaCore/Vision/Battle/BattleRecruitSupportAnalyzer.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "BattleRecruitSupportAnalyzer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
#include "Config/Miscellaneous/BattleDataConfig.h"
|
||||
#include "Config/TaskData.h"
|
||||
#include "Controller/Controller.h"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/NoWarningCV.h"
|
||||
#include "Vision/Matcher.h"
|
||||
#include "Vision/OCRer.h"
|
||||
#include "Vision/RegionOCRer.h"
|
||||
#include "Vision/TemplDetOCRer.h"
|
||||
|
||||
bool asst::BattleRecruitSupportAnalyzer::analyze()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (m_mode == battle::roguelike::SupportAnalyzeMode::ChooseSupportBtn) {
|
||||
// 识别“选择助战”
|
||||
OCRer 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 == battle::roguelike::SupportAnalyzeMode::AnalyzeChars) {
|
||||
// 识别干员
|
||||
OCRer analyzer(m_image);
|
||||
analyzer.set_roi(Task.get("RoguelikeRecruitSupportOcr")->roi);
|
||||
analyzer.set_required(m_required);
|
||||
analyzer.set_replace(
|
||||
Task.get<OcrTaskInfo>("CharsNameOcrReplace")->replace_map,
|
||||
Task.get<OcrTaskInfo>("CharsNameOcrReplace")->replace_full);
|
||||
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());
|
||||
|
||||
battle::roguelike::RecruitSupportCharInfo 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_info.oper_info.elite,
|
||||
char_info.oper_info.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 == battle::roguelike::SupportAnalyzeMode::RefreshSupportBtn) {
|
||||
// 识别“更新助战列表”
|
||||
OCRer analyzer(m_image);
|
||||
|
||||
// 未处在冷却时间
|
||||
analyzer.set_task_info("RoguelikeRefreshSupportBtnOcr");
|
||||
if (analyzer.analyze()) {
|
||||
m_refresh_result = { analyzer.get_result().front().rect, false, 0 };
|
||||
return true;
|
||||
}
|
||||
|
||||
// 刷新冷却中
|
||||
analyzer.set_required({});
|
||||
analyzer.set_replace({ { ":", ":" } });
|
||||
if (!analyzer.analyze()) {
|
||||
Log.info(__FUNCTION__, "| RefreshSupportBtn analyse failed");
|
||||
return false;
|
||||
}
|
||||
const auto& results = analyzer.get_result();
|
||||
for (const auto& result : results) {
|
||||
Log.info(__FUNCTION__, "| RefreshSupportBtn parse `", result.text, "`", result.score);
|
||||
std::smatch match_results;
|
||||
if (std::regex_search(result.text, match_results, std::regex("[0-9]{2}:[0-9]{2}:[0-9]{2}"))) {
|
||||
const auto& match_str = match_results[0].str();
|
||||
const auto& hour = std::atoi(match_str.substr(2).c_str());
|
||||
const auto& min = std::atoi(match_str.substr(3, 2).c_str());
|
||||
const auto& sec = std::atoi(match_str.substr(7, 2).c_str());
|
||||
m_refresh_result = { result.rect, true, 3600 * hour + 60 * min + sec };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Log.info(__FUNCTION__, "| RefreshSupportBtn failed: no matched reusults");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int asst::BattleRecruitSupportAnalyzer::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) {
|
||||
Matcher 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_thresholds.front());
|
||||
|
||||
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::BattleRecruitSupportAnalyzer::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::BattleRecruitSupportAnalyzer::match_level(const Rect& roi)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
RegionOCRer analyzer(m_image);
|
||||
analyzer.set_task_info("NumberOcrReplace");
|
||||
analyzer.set_roi(roi);
|
||||
analyzer.set_bin_expansion(1);
|
||||
|
||||
if (!analyzer.analyze()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Log.info(__FUNCTION__, "| ", roi, "`", analyzer.get_result().text, "`");
|
||||
const std::string& level = analyzer.get_result().text;
|
||||
if (level.empty() || !ranges::all_of(level, [](char c) -> bool { return std::isdigit(c); })) {
|
||||
return 0;
|
||||
}
|
||||
return std::stoi(level);
|
||||
}
|
||||
37
src/MaaCore/Vision/Battle/BattleRecruitSupportAnalyzer.h
Normal file
37
src/MaaCore/Vision/Battle/BattleRecruitSupportAnalyzer.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "Vision/VisionHelper.h"
|
||||
|
||||
#include "Common/AsstBattleDef.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class BattleRecruitSupportAnalyzer final : public VisionHelper
|
||||
{
|
||||
public:
|
||||
using VisionHelper::VisionHelper;
|
||||
virtual ~BattleRecruitSupportAnalyzer() noexcept override = default;
|
||||
|
||||
bool analyze();
|
||||
|
||||
void set_mode(battle::roguelike::SupportAnalyzeMode 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:
|
||||
battle::roguelike::SupportAnalyzeMode m_mode = battle::roguelike::SupportAnalyzeMode::ChooseSupportBtn;
|
||||
Rect m_choose_support_result;
|
||||
battle::roguelike::RefreshSupportInfo m_refresh_result;
|
||||
std::vector<battle::roguelike::RecruitSupportCharInfo> 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