feat.支持肉鸽干员技能选择字段

This commit is contained in:
MistEO
2022-03-13 19:19:19 +08:00
parent 92c27fcaa3
commit cd007ee786
23 changed files with 364 additions and 33 deletions

Binary file not shown.

View File

@@ -1 +1 @@
ch_PP-OCRv2_det_slim_quant_infer
ch_PP-OCRv2_det_infer

View File

@@ -127,8 +127,9 @@ TaskId ASSTAPI AsstAppendTask(AsstHandle handle, const char* type, const char* p
"opers": [ // 会去选择的干员,必选项。数组有序
{
"name": string, // 干员名,必选项
"fullMatch": bool, // 干员名是否需要全字匹配,可选项。默认 false
"skill": int, // 使用哪个技能,取值范围 [1, 3],可选项,默认 1
// 若设置为 3, 当 3 技能不可用时,会选择 2 技能
// 同理若设置为 2, 当 2 技能不可用时,会选择 1 技能(好像没有这种 case
},
...
]

View File

@@ -3933,6 +3933,10 @@
[
"蛇居箱",
"蛇屠箱"
],
[
".+刺",
"棘刺"
]
],
"roi": [
@@ -4604,8 +4608,57 @@
"Roguelike1StartAction"
]
},
"Roguelike1SkillSelectionFlag": {
"templThreshold": 0.9,
"maskRange": [
1,
255
]
},
"Roguelike1SkillSelectionName": {
"algorithm": "OcrDetect",
"text": [],
"action": "DoNothing",
"ocrReplace": [
[
"白面.+",
"白面鸮"
],
[
"蛇居箱",
"蛇屠箱"
],
[
".+刺",
"棘刺"
]
],
"rectMove": [
150,
-30,
180,
65
]
},
"Roguelike1SkillSelectionRect": {
"template": "empty.png",
"roi": [
171,
2,
170,
3
],
"rectMove": [
5,
-45,
45,
45
],
"templThreshold": 60,
"Doc": "这里的阈值作为平均灰度值(亮度)阈值使用"
},
"Roguelike1StartAction": {
"Doc": "本任务注册了插件 RoguelikeBattleTaskPlugin",
"Doc": "本任务注册了插件 RoguelikeSkillSelectionTaskPlugin、RoguelikeBattleTaskPlugin",
"action": "ClickSelf",
"roi": [
905,

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -19,6 +19,9 @@
#include "InfrastTask.h"
#include "RecruitTask.h"
#include "RoguelikeTask.h"
#ifdef ASST_DEBUG
#include "DebugTask.h"
#endif
using namespace asst;
@@ -104,6 +107,11 @@ asst::Assistant::TaskId asst::Assistant::append_task(const std::string& type, co
else if (type == RoguelikeTask::TaskType) {
ptr = std::make_shared<RoguelikeTask>(task_callback, (void*)this);
}
#ifdef ASST_DEBUG
else if (type == DebugTask::TaskType) {
ptr = std::make_shared<DebugTask>(task_callback, (void*)this);
}
#endif
else {
Log.error(__FUNCTION__, "| invalid type:", type);
return 0;

View File

@@ -0,0 +1,10 @@
#include "DebugTask.h"
#include "RoguelikeSkillSelectionTaskPlugin.h"
asst::DebugTask::DebugTask(AsstCallback callback, void* callback_arg)
: PackageTask(callback, callback_arg, TaskType)
{
auto task_ptr = std::make_shared<RoguelikeSkillSelectionTaskPlugin>(callback, callback_arg, TaskType);
m_subtasks.emplace_back(task_ptr);
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include "PackageTask.h"
namespace asst
{
class DebugTask : public PackageTask
{
public:
DebugTask(AsstCallback callback, void* callback_arg);
virtual ~DebugTask() = default;
static constexpr const char* TaskType = "Debug";
};
}

View File

@@ -27,6 +27,7 @@
<ClInclude Include="AwardTask.h" />
<ClInclude Include="BattleImageAnalyzer.h" />
<ClInclude Include="BattlePerspectiveImageAnalyzer.h" />
<ClInclude Include="DebugTask.h" />
<ClInclude Include="FightTask.h" />
<ClInclude Include="InfrastTask.h" />
<ClInclude Include="MallTask.h" />
@@ -70,6 +71,8 @@
<ClInclude Include="RoguelikeFormationTaskPlugin.h" />
<ClInclude Include="RoguelikeFormationImageAnalyzer.h" />
<ClInclude Include="RoguelikeRecruitTaskPlugin.h" />
<ClInclude Include="RoguelikeSkillSelectionImageAnalyzer.h" />
<ClInclude Include="RoguelikeSkillSelectionTaskPlugin.h" />
<ClInclude Include="RoguelikeTask.h" />
<ClInclude Include="RuntimeStatus.h" />
<ClInclude Include="StageDropsTaskPlugin.h" />
@@ -92,6 +95,7 @@
<ClCompile Include="AwardTask.cpp" />
<ClCompile Include="BattleImageAnalyzer.cpp" />
<ClCompile Include="BattlePerspectiveImageAnalyzer.cpp" />
<ClCompile Include="DebugTask.cpp" />
<ClCompile Include="FightTask.cpp" />
<ClCompile Include="InfrastTask.cpp" />
<ClCompile Include="MallTask.cpp" />
@@ -136,6 +140,8 @@
<ClCompile Include="RoguelikeFormationTaskPlugin.cpp" />
<ClCompile Include="RoguelikeFormationImageAnalyzer.cpp" />
<ClCompile Include="RoguelikeRecruitTaskPlugin.cpp" />
<ClCompile Include="RoguelikeSkillSelectionImageAnalyzer.cpp" />
<ClCompile Include="RoguelikeSkillSelectionTaskPlugin.cpp" />
<ClCompile Include="RoguelikeTask.cpp" />
<ClCompile Include="RuntimeStatus.cpp" />
<ClCompile Include="StageDropsTaskPlugin.cpp" />

View File

@@ -294,6 +294,15 @@
<ClInclude Include="RoguelikeRecruitTaskPlugin.h">
<Filter>头文件\Task\Plugin</Filter>
</ClInclude>
<ClInclude Include="RoguelikeSkillSelectionTaskPlugin.h">
<Filter>头文件\Task\Plugin</Filter>
</ClInclude>
<ClInclude Include="DebugTask.h">
<Filter>头文件\Task\Plugin</Filter>
</ClInclude>
<ClInclude Include="RoguelikeSkillSelectionImageAnalyzer.h">
<Filter>头文件\ImageAnalyzer\Final</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Controller.cpp">
@@ -485,6 +494,15 @@
<ClCompile Include="RoguelikeRecruitTaskPlugin.cpp">
<Filter>源文件\Task\Plugin</Filter>
</ClCompile>
<ClCompile Include="RoguelikeSkillSelectionTaskPlugin.cpp">
<Filter>源文件\Task\Plugin</Filter>
</ClCompile>
<ClCompile Include="DebugTask.cpp">
<Filter>源文件\Task</Filter>
</ClCompile>
<ClCompile Include="RoguelikeSkillSelectionImageAnalyzer.cpp">
<Filter>源文件\ImageAnalyzer\Final</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\..\resource\config.json">

View File

@@ -126,6 +126,14 @@ bool asst::MultiMatchImageAnalyzer::multi_match_templ(const cv::Mat templ)
}
}
}
#ifdef ASST_DEBUG
for (const auto& rect : m_result) {
cv::rectangle(m_image_draw, utils::make_rect<cv::Rect>(rect.rect), cv::Scalar(0, 0, 255), 2);
cv::putText(m_image_draw, std::to_string(rect.score), cv::Point(rect.rect.x, rect.rect.y), 1, 1, cv::Scalar(0, 0, 255));
}
#endif
std::string log_str = "[ ";
for (const auto& res : m_result) {
log_str += res.rect.to_string() + " : " + std::to_string(res.score) + "; ";

View File

@@ -145,6 +145,15 @@ void asst::OcrImageAnalyzer::sort_result()
);
}
void asst::OcrImageAnalyzer::sort_result_by_score()
{
std::sort(m_ocr_result.begin(), m_ocr_result.end(),
[](const TextRect& lhs, const TextRect& rhs) -> bool {
return lhs.score < rhs.score;
}
);
}
void asst::OcrImageAnalyzer::sort_result_by_required()
{
if (m_required.empty()) {

View File

@@ -18,6 +18,7 @@ namespace asst
virtual bool analyze() override;
void sort_result(); // 按位置排序,左上角的排在前面
void sort_result_by_score(); // 按分数排序,得分最高的在前面
void sort_result_by_required(); // 按传入的需求数组排序,传入的在前面结果接在前面
void set_use_cache(bool is_use) noexcept;

View File

@@ -20,12 +20,6 @@ bool asst::RoguelikeFormationTaskPlugin::verify(AsstMsg msg, const json::value&
}
}
bool asst::RoguelikeFormationTaskPlugin::set_operators(std::vector<BattleOper> opers)
{
m_opers = std::move(opers);
return true;
}
bool asst::RoguelikeFormationTaskPlugin::_run()
{
RoguelikeFormationImageAnalyzer formation_analyzer(m_ctrler->get_image());

View File

@@ -3,12 +3,6 @@
namespace asst
{
struct BattleOper
{
std::string name;
bool full_match = false;
int skill = 1;
};
// 集成战略模式快捷编队任务
class RoguelikeFormationTaskPlugin : public AbstractTaskPlugin
{
@@ -18,11 +12,7 @@ namespace asst
virtual bool verify(AsstMsg msg, const json::value& details) const override;
bool set_operators(std::vector<BattleOper> opers);
protected:
virtual bool _run() override;
std::vector<BattleOper> m_opers;
};
}

View File

@@ -0,0 +1,77 @@
#include "RoguelikeSkillSelectionImageAnalyzer.h"
#include "AsstUtils.hpp"
#include "OcrImageAnalyzer.h"
#include "MultiMatchImageAnalyzer.h"
#include "TaskData.h"
#include "Logger.hpp"
bool asst::RoguelikeSkillSelectionImageAnalyzer::analyze()
{
MultiMatchImageAnalyzer flag_analyzer(m_image);
flag_analyzer.set_task_info(Task.get("Roguelike1SkillSelectionFlag"));
if (!flag_analyzer.analyze()) {
return false;
}
const auto& flags = flag_analyzer.get_result();
int unknow_index = 0;
for (const auto& flag : flags) {
std::string name = name_analyze(flag.rect);
auto skills = skill_analyze(flag.rect);
if (skills.empty()) {
continue;
}
if (name.empty()) {
name = "Unknown" + std::to_string(unknow_index++);
}
m_result.emplace(std::move(name), std::move(skills));
}
return !m_result.empty();
}
std::string asst::RoguelikeSkillSelectionImageAnalyzer::name_analyze(const Rect& roi)
{
OcrImageAnalyzer analyzer;
auto name_task_ptr = Task.get("Roguelike1SkillSelectionName");
analyzer.set_task_info(name_task_ptr);
analyzer.set_image(m_image, roi.move(name_task_ptr->rect_move));
if (!analyzer.analyze()) {
return std::string();
}
analyzer.sort_result_by_score();
return analyzer.get_result().front().text;
}
std::vector<asst::Rect> asst::RoguelikeSkillSelectionImageAnalyzer::skill_analyze(const Rect& roi)
{
const auto task_ptr = std::dynamic_pointer_cast<MatchTaskInfo>(Task.get("Roguelike1SkillSelectionRect"));
Rect cor_roi = roi.move(task_ptr->roi);
cv::Mat image_roi = m_image(utils::make_rect<cv::Rect>(cor_roi));
std::vector<Rect> result_vec;
int skill_width = cor_roi.width / MaxNumOfSkills;
for (int x = 0; x + skill_width < cor_roi.width; x += skill_width) {
Rect single_skill_rect(x, 0, skill_width, cor_roi.height);
cv::Mat skill = image_roi(utils::make_rect<cv::Rect>(single_skill_rect));
cv::Mat skill_gray;
cv::cvtColor(skill, skill_gray, cv::COLOR_BGR2GRAY);
cv::Scalar avg = cv::mean(skill_gray);
Log.trace(__FUNCTION__, "gray avg", avg[0]);
if (avg[0] < task_ptr->templ_threshold) {
break;
}
Rect result = cor_roi.move(single_skill_rect.move(task_ptr->rect_move));
#ifdef ASST_DEBUG
cv::rectangle(m_image_draw, utils::make_rect<cv::Rect>(result), cv::Scalar(0, 255, 0), 3);
#endif
result_vec.emplace_back(result);
}
return result_vec;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "AbstractImageAnalyzer.h"
namespace asst
{
class RoguelikeSkillSelectionImageAnalyzer final : public AbstractImageAnalyzer
{
public:
static constexpr size_t MaxNumOfSkills = 3;
public:
using AbstractImageAnalyzer::AbstractImageAnalyzer;
virtual ~RoguelikeSkillSelectionImageAnalyzer() = default;
virtual bool analyze() override;
const auto& get_result() const noexcept
{
return m_result;
}
private:
// 该分析器不支持外部设置ROI
virtual void set_roi(const Rect& roi) noexcept override
{
AbstractImageAnalyzer::set_roi(roi);
}
virtual void set_image(const cv::Mat image, const Rect& roi)
{
AbstractImageAnalyzer::set_image(image, roi);
}
std::string name_analyze(const Rect& roi);
std::vector<Rect> skill_analyze(const Rect& roi);
std::unordered_map<std::string, std::vector<Rect>> m_result; // 干员名 : 技能的位置(多个技能)
};
}

View File

@@ -0,0 +1,67 @@
#include "RoguelikeSkillSelectionTaskPlugin.h"
#include "RoguelikeSkillSelectionImageAnalyzer.h"
#include "Controller.h"
#include "TaskData.h"
#include "Logger.hpp"
bool asst::RoguelikeSkillSelectionTaskPlugin::verify(AsstMsg msg, const json::value& details) const
{
if (msg != AsstMsg::SubTaskStart
|| details.get("subtask", std::string()) != "ProcessTask") {
return false;
}
if (details.at("details").at("task").as_string() == "Roguelike1StartAction") {
return true;
}
else {
return false;
}
}
void asst::RoguelikeSkillSelectionTaskPlugin::set_skill_map(SkillMap skill_mapping)
{
m_skill_mapping = std::move(skill_mapping);
}
bool asst::RoguelikeSkillSelectionTaskPlugin::_run()
{
//#ifdef ASST_DEBUG
// m_skill_mapping.clear();
// m_skill_mapping.reserve(4);
// m_skill_mapping.emplace("棘刺", 3);
// m_skill_mapping.emplace("芙蓉", 1);
// m_skill_mapping.emplace("梓兰", 1);
// m_skill_mapping.emplace("阿米娅", 3);
// m_skill_mapping.emplace("Unknown", 3);
//#endif
auto image = m_ctrler->get_image();
RoguelikeSkillSelectionImageAnalyzer analyzer(image);
if (!analyzer.analyze()) {
return false;
}
for (const auto& [name, skill_vec] : analyzer.get_result()) {
for (const auto& [req_name, req_skill] : m_skill_mapping) {
if (name.find(req_name) == std::string::npos) {
continue;
}
size_t index = 0;
if (skill_vec.size() < req_skill) {
index = skill_vec.size() - 1;
Log.info("skill", req_skill, "not exists, use skill", index);
}
else {
index = req_skill - 1;
}
Rect target = skill_vec.at(index);
m_ctrler->click(target);
break;
}
}
return true;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "AbstractTaskPlugin.h"
#include <unordered_map>
namespace asst
{
class RoguelikeSkillSelectionTaskPlugin final : public AbstractTaskPlugin
{
public:
using SkillMap = std::unordered_map<std::string, int>;
public:
using AbstractTaskPlugin::AbstractTaskPlugin;
virtual ~RoguelikeSkillSelectionTaskPlugin() = default;
virtual bool verify(AsstMsg msg, const json::value& details) const override;
void set_skill_map(SkillMap skill_mapping);
protected:
virtual bool _run() override;
SkillMap m_skill_mapping;
};
}

View File

@@ -4,6 +4,7 @@
#include "RoguelikeFormationTaskPlugin.h"
#include "RoguelikeBattleTaskPlugin.h"
#include "RoguelikeRecruitTaskPlugin.h"
#include "RoguelikeSkillSelectionTaskPlugin.h"
#include "Logger.hpp"
@@ -19,6 +20,9 @@ asst::RoguelikeTask::RoguelikeTask(AsstCallback callback, void* callback_arg)
m_recruit_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeRecruitTaskPlugin>();
m_recruit_task_ptr->set_retry_times(2);
m_skill_task_ptr = m_roguelike_task_ptr->regiseter_plugin<RoguelikeSkillSelectionTaskPlugin>();
m_skill_task_ptr->set_retry_times(0);
// 这个任务如果卡住会放弃当前的肉鸽并重新开始,所以多添加一点。先这样凑合用
for (int i = 0; i != 10000; ++i) {
m_subtasks.emplace_back(m_roguelike_task_ptr);
@@ -30,23 +34,29 @@ bool asst::RoguelikeTask::set_params(const json::value& params)
// 0 - 尽可能一直往后打
// 1 - 第一层投资完源石锭就退出
// 2 - 投资过后再退出,没有投资就继续往后打
int mode = params.get("mode", 0);
std::vector<std::string> opers_vec;
if (params.contains("opers") && params.at("opers").is_array()) {
for (auto& oper : params.at("opers").as_array()) {
if (oper.contains("name") && oper.at("name").is_string()) {
opers_vec.emplace_back(oper.at("name").as_string());
}
else {
return false;
}
}
}
else {
if (!params.contains("opers") || !params.at("opers").is_array()) {
return false;
}
std::vector<std::string> opers_vec;
RoguelikeSkillSelectionTaskPlugin::SkillMap skill_map;
for (auto& oper : params.at("opers").as_array()) {
if (!oper.contains("name") || !oper.at("name").is_string()) {
return false;
}
int skill = oper.get("skill", 1);
if (skill < 1 || 3 < skill) {
return false;
}
std::string name = oper.at("name").as_string();
opers_vec.emplace_back(name);
skill_map.emplace(name, skill);
}
skill_map.emplace("Unknown", 3);
int mode = params.get("mode", 0);
switch (mode) {
case 0:
break;
@@ -62,6 +72,7 @@ bool asst::RoguelikeTask::set_params(const json::value& params)
}
m_recruit_task_ptr->set_opers(std::move(opers_vec));
m_skill_task_ptr->set_skill_map(std::move(skill_map));
return true;
}

View File

@@ -5,6 +5,7 @@ namespace asst
{
class ProcessTask;
class RoguelikeRecruitTaskPlugin;
class RoguelikeSkillSelectionTaskPlugin;
class RoguelikeTask : public PackageTask
{
@@ -19,5 +20,6 @@ namespace asst
private:
std::shared_ptr<ProcessTask> m_roguelike_task_ptr = nullptr;
std::shared_ptr<RoguelikeRecruitTaskPlugin> m_recruit_task_ptr = nullptr;
std::shared_ptr<RoguelikeSkillSelectionTaskPlugin> m_skill_task_ptr = nullptr;
};
}

View File

@@ -12,7 +12,7 @@ namespace asst
virtual ~StartUpTask() = default;
static constexpr const char* TaskType = "StartUp";
protected:
private:
std::shared_ptr<ProcessTask> m_start_up_task_ptr = nullptr;
};