diff --git a/3rdparty/resource/PaddleOCR/det/inference.pdiparams b/3rdparty/resource/PaddleOCR/det/inference.pdiparams index df26a3f2e5..0794fa26d8 100644 Binary files a/3rdparty/resource/PaddleOCR/det/inference.pdiparams and b/3rdparty/resource/PaddleOCR/det/inference.pdiparams differ diff --git a/3rdparty/resource/PaddleOCR/det/inference.pdmodel b/3rdparty/resource/PaddleOCR/det/inference.pdmodel index f987ac0ebe..213147e488 100644 Binary files a/3rdparty/resource/PaddleOCR/det/inference.pdmodel and b/3rdparty/resource/PaddleOCR/det/inference.pdmodel differ diff --git a/3rdparty/resource/PaddleOCR/det/version.txt b/3rdparty/resource/PaddleOCR/det/version.txt index 02c07004e9..c5193c02b3 100644 --- a/3rdparty/resource/PaddleOCR/det/version.txt +++ b/3rdparty/resource/PaddleOCR/det/version.txt @@ -1 +1 @@ -ch_PP-OCRv2_det_slim_quant_infer +ch_PP-OCRv2_det_infer \ No newline at end of file diff --git a/docs/集成文档.md b/docs/集成文档.md index c4cb1279b4..684a8c8cf0 100644 --- a/docs/集成文档.md +++ b/docs/集成文档.md @@ -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?) }, ... ] diff --git a/resource/tasks.json b/resource/tasks.json index 2f65c99181..ef67cb1c71 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -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, diff --git a/resource/template/Roguelike1SkillSelectionFlag.png b/resource/template/Roguelike1SkillSelectionFlag.png new file mode 100644 index 0000000000..25f9565a1b Binary files /dev/null and b/resource/template/Roguelike1SkillSelectionFlag.png differ diff --git a/src/MeoAssistant/Assistant.cpp b/src/MeoAssistant/Assistant.cpp index e11fb3eeb0..57f3ea30d9 100644 --- a/src/MeoAssistant/Assistant.cpp +++ b/src/MeoAssistant/Assistant.cpp @@ -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(task_callback, (void*)this); } +#ifdef ASST_DEBUG + else if (type == DebugTask::TaskType) { + ptr = std::make_shared(task_callback, (void*)this); + } +#endif else { Log.error(__FUNCTION__, "| invalid type:", type); return 0; diff --git a/src/MeoAssistant/DebugTask.cpp b/src/MeoAssistant/DebugTask.cpp new file mode 100644 index 0000000000..9af1a5ba3f --- /dev/null +++ b/src/MeoAssistant/DebugTask.cpp @@ -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(callback, callback_arg, TaskType); + m_subtasks.emplace_back(task_ptr); +} diff --git a/src/MeoAssistant/DebugTask.h b/src/MeoAssistant/DebugTask.h new file mode 100644 index 0000000000..0135e4c6cc --- /dev/null +++ b/src/MeoAssistant/DebugTask.h @@ -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"; + }; +} diff --git a/src/MeoAssistant/MeoAssistant.vcxproj b/src/MeoAssistant/MeoAssistant.vcxproj index 33e1c4d044..dea8a1dd86 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj +++ b/src/MeoAssistant/MeoAssistant.vcxproj @@ -27,6 +27,7 @@ + @@ -70,6 +71,8 @@ + + @@ -92,6 +95,7 @@ + @@ -136,6 +140,8 @@ + + diff --git a/src/MeoAssistant/MeoAssistant.vcxproj.filters b/src/MeoAssistant/MeoAssistant.vcxproj.filters index 9a7a40c098..d981ddbcb7 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj.filters +++ b/src/MeoAssistant/MeoAssistant.vcxproj.filters @@ -294,6 +294,15 @@ 头文件\Task\Plugin + + 头文件\Task\Plugin + + + 头文件\Task\Plugin + + + 头文件\ImageAnalyzer\Final + @@ -485,6 +494,15 @@ 源文件\Task\Plugin + + 源文件\Task\Plugin + + + 源文件\Task + + + 源文件\ImageAnalyzer\Final + diff --git a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp index 55a2a6c2a5..9b2ba6dcb5 100644 --- a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp +++ b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp @@ -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(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) + "; "; diff --git a/src/MeoAssistant/OcrImageAnalyzer.cpp b/src/MeoAssistant/OcrImageAnalyzer.cpp index bad6657704..4b6435f822 100644 --- a/src/MeoAssistant/OcrImageAnalyzer.cpp +++ b/src/MeoAssistant/OcrImageAnalyzer.cpp @@ -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()) { diff --git a/src/MeoAssistant/OcrImageAnalyzer.h b/src/MeoAssistant/OcrImageAnalyzer.h index 12aae9e633..981b30668d 100644 --- a/src/MeoAssistant/OcrImageAnalyzer.h +++ b/src/MeoAssistant/OcrImageAnalyzer.h @@ -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; diff --git a/src/MeoAssistant/RoguelikeFormationTaskPlugin.cpp b/src/MeoAssistant/RoguelikeFormationTaskPlugin.cpp index 36b9483472..6554a49154 100644 --- a/src/MeoAssistant/RoguelikeFormationTaskPlugin.cpp +++ b/src/MeoAssistant/RoguelikeFormationTaskPlugin.cpp @@ -20,12 +20,6 @@ bool asst::RoguelikeFormationTaskPlugin::verify(AsstMsg msg, const json::value& } } -bool asst::RoguelikeFormationTaskPlugin::set_operators(std::vector opers) -{ - m_opers = std::move(opers); - return true; -} - bool asst::RoguelikeFormationTaskPlugin::_run() { RoguelikeFormationImageAnalyzer formation_analyzer(m_ctrler->get_image()); diff --git a/src/MeoAssistant/RoguelikeFormationTaskPlugin.h b/src/MeoAssistant/RoguelikeFormationTaskPlugin.h index f75250dd3d..a72f11db46 100644 --- a/src/MeoAssistant/RoguelikeFormationTaskPlugin.h +++ b/src/MeoAssistant/RoguelikeFormationTaskPlugin.h @@ -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 opers); - protected: virtual bool _run() override; - - std::vector m_opers; }; } diff --git a/src/MeoAssistant/RoguelikeSkillSelectionImageAnalyzer.cpp b/src/MeoAssistant/RoguelikeSkillSelectionImageAnalyzer.cpp new file mode 100644 index 0000000000..41bec32163 --- /dev/null +++ b/src/MeoAssistant/RoguelikeSkillSelectionImageAnalyzer.cpp @@ -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::RoguelikeSkillSelectionImageAnalyzer::skill_analyze(const Rect& roi) +{ + const auto task_ptr = std::dynamic_pointer_cast(Task.get("Roguelike1SkillSelectionRect")); + Rect cor_roi = roi.move(task_ptr->roi); + cv::Mat image_roi = m_image(utils::make_rect(cor_roi)); + + std::vector 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(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(result), cv::Scalar(0, 255, 0), 3); +#endif + result_vec.emplace_back(result); + } + + return result_vec; +} diff --git a/src/MeoAssistant/RoguelikeSkillSelectionImageAnalyzer.h b/src/MeoAssistant/RoguelikeSkillSelectionImageAnalyzer.h new file mode 100644 index 0000000000..103aac83d8 --- /dev/null +++ b/src/MeoAssistant/RoguelikeSkillSelectionImageAnalyzer.h @@ -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 skill_analyze(const Rect& roi); + + std::unordered_map> m_result; // 干员名 : 技能的位置(多个技能) + }; +} diff --git a/src/MeoAssistant/RoguelikeSkillSelectionTaskPlugin.cpp b/src/MeoAssistant/RoguelikeSkillSelectionTaskPlugin.cpp new file mode 100644 index 0000000000..b4124a5bd0 --- /dev/null +++ b/src/MeoAssistant/RoguelikeSkillSelectionTaskPlugin.cpp @@ -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; +} diff --git a/src/MeoAssistant/RoguelikeSkillSelectionTaskPlugin.h b/src/MeoAssistant/RoguelikeSkillSelectionTaskPlugin.h new file mode 100644 index 0000000000..64b198edc9 --- /dev/null +++ b/src/MeoAssistant/RoguelikeSkillSelectionTaskPlugin.h @@ -0,0 +1,25 @@ +#pragma once +#include "AbstractTaskPlugin.h" + +#include + +namespace asst +{ + class RoguelikeSkillSelectionTaskPlugin final : public AbstractTaskPlugin + { + public: + using SkillMap = std::unordered_map; + 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; + }; +} diff --git a/src/MeoAssistant/RoguelikeTask.cpp b/src/MeoAssistant/RoguelikeTask.cpp index 5493682739..329f501f32 100644 --- a/src/MeoAssistant/RoguelikeTask.cpp +++ b/src/MeoAssistant/RoguelikeTask.cpp @@ -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(); m_recruit_task_ptr->set_retry_times(2); + m_skill_task_ptr = m_roguelike_task_ptr->regiseter_plugin(); + 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 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 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; } diff --git a/src/MeoAssistant/RoguelikeTask.h b/src/MeoAssistant/RoguelikeTask.h index 30e760552d..447f448924 100644 --- a/src/MeoAssistant/RoguelikeTask.h +++ b/src/MeoAssistant/RoguelikeTask.h @@ -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 m_roguelike_task_ptr = nullptr; std::shared_ptr m_recruit_task_ptr = nullptr; + std::shared_ptr m_skill_task_ptr = nullptr; }; } diff --git a/src/MeoAssistant/StartUpTask.h b/src/MeoAssistant/StartUpTask.h index 801f77d398..c508d01701 100644 --- a/src/MeoAssistant/StartUpTask.h +++ b/src/MeoAssistant/StartUpTask.h @@ -12,7 +12,7 @@ namespace asst virtual ~StartUpTask() = default; static constexpr const char* TaskType = "StartUp"; - protected: + private: std::shared_ptr m_start_up_task_ptr = nullptr; };