diff --git a/resource/tasks.json b/resource/tasks.json index c31575a93f..ec2ea0f6d6 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -3299,6 +3299,121 @@ "3ffc7ffe7c3ff00ff007f007f00ffc0f7fff3ffe1ffe00fc01f803f00ff00fc0" ] }, + "BattleHpFlag": { + "roi": [ + 400, + 0, + 650, + 60 + ], + "rectMove": [ + 40, + 0, + 100, + 35 + ] + }, + "BattleHpFlag2": { + "roi": [ + 400, + 0, + 650, + 60 + ], + "rectMove": [ + 40, + 0, + 100, + 35 + ] + }, + "BattleHpChannelH": { + "algorithm": "hash", + "hash": [ + ], + "maskRange": [ + 0, + 1 + ] + }, + "BattleHpChannelS": { + "algorithm": "hash", + "hash": [ + ], + "maskRange": [ + 120, + 150 + ] + }, + "BattleHpChannelV": { + "algorithm": "hash", + "hash": [ + ], + "maskRange": [ + 170, + 255 + ] + }, + "BattleHp0": { + "algorithm": "hash", + "hash": [ + "03e007fc3c0638066003400140014001c001400140016003600338061e1c07f8" + ] + }, + "BattleHp1": { + "algorithm": "hash", + "hash": [ + "003f01ff1ffffe0f000700070007000700070007000700070007000700070007" + ] + }, + "BattleHp2": { + "algorithm": "hash", + "hash": [ + "07e03c3c2007c0030003000300030007001c003800e003c004003800f800ffff" + ] + }, + "BattleHp3": { + "algorithm": "hash", + "hash": [ + "0ff07e78700ee00700070006000e03f80078000e000700078007e00e703e1ff8" + ] + }, + "BattleHp4": { + "algorithm": "hash", + "hash": [ + "0010003c007c007c01dc039c061c0c1c381c301c783effff001c001c001c001c" + ] + }, + "BattleHp5": { + "algorithm": "hash", + "hash": [ + "1ff87ffe7000600060006000fff0f038e00e0006000700078007e00e703e1ff0" + ] + }, + "BattleHp6": { + "algorithm": "hash", + "hash": [ + "018002000e000c0010003fe07808f004c006800380018003c007e006300c1ff0" + ] + }, + "BattleHp7": { + "algorithm": "hash", + "hash": [ + "7fffffff00070006000e003800380070004001c0018003800e000e001c001000" + ] + }, + "BattleHp8": { + "algorithm": "hash", + "hash": [ + "0ff01038600ee006e006600670081ff01c7860068007800180018007600e1ff8" + ] + }, + "BattleHp9": { + "algorithm": "hash", + "hash": [ + "07e01e78700e6006e007e007e0076006700e1ffe011800700060008001800700" + ] + }, "BattleSpeedUp": { "action": "clickSelf", "roi": [ @@ -3307,11 +3422,43 @@ 149, 153 ], - "maksRange": [ + "maskRange": [ 100, 255 + ], + "templThreshold": 0.95, + "preDelay": 2000 + }, + "BattleSkillReady": { + "roi": [ + 0, + 0, + 1280, + 580 + ], + "maskRange": [ + 1, + 255 + ], + "rectMove": [ + 0, + 100, + 40, + 40 ] }, + "BattleReleaseSkill": { + "algorithm": "justReturn", + "action": "clickRect", + "specificRect": [ + 820, + 380, + 100, + 100 + ], + "preDelay": 1000, + "rearDelay": 500 + }, "Roguelike1Start": { "action": "clickSelf", "roi": [ diff --git a/resource/template/BattleHpFlag.png b/resource/template/BattleHpFlag.png new file mode 100644 index 0000000000..928ec85c66 Binary files /dev/null and b/resource/template/BattleHpFlag.png differ diff --git a/resource/template/BattleHpFlag2.png b/resource/template/BattleHpFlag2.png new file mode 100644 index 0000000000..b898cf012d Binary files /dev/null and b/resource/template/BattleHpFlag2.png differ diff --git a/resource/template/BattleSkillReady.png b/resource/template/BattleSkillReady.png new file mode 100644 index 0000000000..8b33c8b382 Binary files /dev/null and b/resource/template/BattleSkillReady.png differ diff --git a/src/MeoAssistant/BattleImageAnalyzer.cpp b/src/MeoAssistant/BattleImageAnalyzer.cpp index ee4300cd38..aa4e1891da 100644 --- a/src/MeoAssistant/BattleImageAnalyzer.cpp +++ b/src/MeoAssistant/BattleImageAnalyzer.cpp @@ -10,7 +10,10 @@ bool asst::BattleImageAnalyzer::analyze() { - bool ret = opers_analyze(); + opers_analyze(); + skill_analyze(); + // 生命值和家门,只要识别到了任一个,就可以说明当前是在战斗场景 + bool ret = hp_analyze(); ret |= home_analyze(); return ret; } @@ -25,6 +28,16 @@ const std::vector& asst::BattleImageAnalyzer::get_homes() const noex return m_homes; } +const std::vector& asst::BattleImageAnalyzer::get_ready_skills() const noexcept +{ + return m_ready_skills; +} + +int asst::BattleImageAnalyzer::get_hp() const noexcept +{ + return m_hp; +} + bool asst::BattleImageAnalyzer::opers_analyze() { LogTraceFunction; @@ -227,3 +240,89 @@ bool asst::BattleImageAnalyzer::home_analyze() return true; } + +bool asst::BattleImageAnalyzer::skill_analyze() +{ + const auto skill_task_ptr = Task.get("BattleSkillReady"); + const Rect& rect_move = skill_task_ptr->rect_move; + + MultiMatchImageAnalyzer mm_analyzer(m_image); + mm_analyzer.set_task_info(skill_task_ptr); + if (!mm_analyzer.analyze()) { + return false; + } + for (const auto& mr : mm_analyzer.get_result()) { + m_ready_skills.emplace_back(mr.rect.move(rect_move)); + } + return true; +} + +bool asst::BattleImageAnalyzer::hp_analyze() +{ + // 识别 HP 的那个蓝白色图标 + auto flag_task_ptr = Task.get("BattleHpFlag"); + MatchImageAnalyzer flag_analyzer(m_image); + flag_analyzer.set_task_info(flag_task_ptr); + if (!flag_analyzer.analyze()) { + // 漏怪的时候,那个图标会变成红色的,所以多识别一次 + flag_task_ptr = Task.get("BattleHpFlag2"); + flag_analyzer.set_task_info(flag_task_ptr); + if (!flag_analyzer.analyze()) { + return false; + } + } + Rect roi_rect = flag_analyzer.get_result().rect.move(flag_task_ptr->rect_move); + cv::Mat roi = m_image(utils::make_rect(roi_rect)); + + static const std::array NumName = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" + }; + static bool inited = false; + static cv::Scalar range_lower, range_upper; + static HashImageAnalyzer hash_analyzer; + if (!inited) { + auto [h_l, h_u] = std::dynamic_pointer_cast( + Task.get("BattleHpChannelH"))->mask_range; + auto [s_l, s_u] = std::dynamic_pointer_cast( + Task.get("BattleHpChannelS"))->mask_range; + auto [v_l, v_u] = std::dynamic_pointer_cast( + Task.get("BattleHpChannelV"))->mask_range; + range_lower = cv::Scalar(h_l, s_l, v_l); + range_upper = cv::Scalar(h_u, s_u, v_u); + std::unordered_map num_hashs; + for (auto&& num : NumName) { + auto hashs_vec = std::dynamic_pointer_cast( + Task.get("BattleHp" + num))->hashs; + for (size_t i = 0; i != hashs_vec.size(); ++i) { + num_hashs.emplace(num + "_" + std::to_string(i), hashs_vec.at(i)); + } + } + hash_analyzer.set_hash_templates(std::move(num_hashs)); + hash_analyzer.set_need_bound(true); + hash_analyzer.set_need_split(true); + inited = true; + } + + cv::Mat hsv; + cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV); + cv::Mat bin; + cv::inRange(hsv, range_lower, range_upper, bin); + hash_analyzer.set_image(bin); + hash_analyzer.analyze(); + + int hp = 0; + for (const std::string& num_name : hash_analyzer.get_min_dist_name()) { + if (num_name.empty()) { + Log.error("hash result is empty"); + return 0; + } + hp *= 10; + hp += num_name.at(0) - '0'; + } + +#ifdef ASST_DEBUG + cv::putText(m_image_draw, "HP: " + std::to_string(hp), cv::Point(roi_rect.x, roi_rect.y + 50), 2, 1, cv::Scalar(0, 0, 255)); +#endif + m_hp = hp; + return true; +} diff --git a/src/MeoAssistant/BattleImageAnalyzer.h b/src/MeoAssistant/BattleImageAnalyzer.h index 6d49f145aa..6b06c832cc 100644 --- a/src/MeoAssistant/BattleImageAnalyzer.h +++ b/src/MeoAssistant/BattleImageAnalyzer.h @@ -34,16 +34,24 @@ namespace asst virtual const std::vector& get_opers() const noexcept; virtual const std::vector& get_homes() const noexcept; + + const std::vector& get_ready_skills() const noexcept; + int get_hp() const noexcept; protected: bool opers_analyze(); // 识别干员 Role oper_role_analyze(const Rect& roi); int oper_cost_analyze(const Rect& roi); bool oper_available_analyze(const Rect& roi); - bool home_analyze(); // 识别蓝色的家门 - std::vector m_opers; // 下方干员信息 + bool home_analyze(); // 识别蓝色的家门 + bool skill_analyze(); // 识别技能是否可用 + bool hp_analyze(); // 识别剩余生命值 + + std::vector m_opers; // 下方干员信息 std::vector m_homes; // 蓝色的家门位置 + std::vector m_ready_skills; // 可以释放的技能(Rect实际是干员的位置) + int m_hp; // 剩余生命值 protected: // 该分析器不支持外部设置ROI diff --git a/src/MeoAssistant/BattleTask.cpp b/src/MeoAssistant/BattleTask.cpp index 1ce2f8fde8..d5abad2371 100644 --- a/src/MeoAssistant/BattleTask.cpp +++ b/src/MeoAssistant/BattleTask.cpp @@ -6,34 +6,54 @@ #include "TaskData.h" #include "ProcessTask.h" -typedef asst::BattleImageAnalyzer::Role Role; -typedef asst::BattleImageAnalyzer::Oper Oper; - bool asst::BattleTask::_run() { m_used_opers = false; + m_pre_hp = 0; + + speed_up(); while (!need_exit()) { + // 不在战斗场景,且已使用过了干员,说明已经打完了,就结束循环 if (!auto_battle() && m_used_opers) { break; } } - speed_up(); return wait_for_mission_completed(); } bool asst::BattleTask::auto_battle() { - BattleImageAnalyzer oper_analyzer(Ctrler.get_image()); - if (!oper_analyzer.analyze()) { + using Role = asst::BattleImageAnalyzer::Role; + using Oper = asst::BattleImageAnalyzer::Oper; + + //cv::Mat test = cv::imread("f.png"); + BattleImageAnalyzer battle_analyzer(Ctrler.get_image()); + if (!battle_analyzer.analyze()) { return false; } - const auto& opers = oper_analyzer.get_opers(); + + if (int hp = battle_analyzer.get_hp(); + hp != 0) { + bool used_skills = false; + if (hp < m_pre_hp) { // 说明漏怪了,漏怪就开技能( + for (const Rect& rect : battle_analyzer.get_ready_skills()) { + release_skill(rect); + used_skills = true; + } + } + m_pre_hp = hp; + if (used_skills) { + return true; + } + } + + const auto& opers = battle_analyzer.get_opers(); if (opers.empty()) { - return false; + return true; } - if (auto cur_home = oper_analyzer.get_homes(); + if (auto cur_home = battle_analyzer.get_homes(); !cur_home.empty()) { m_home_cache = cur_home; } @@ -147,6 +167,17 @@ bool asst::BattleTask::speed_up() bool asst::BattleTask::wait_for_mission_completed() { - ProcessTask task(*this, { "Roguelike1InBattleFlag" }); + ProcessTask task(*this, { + "Roguelike1InBattleFlag", + "Roguelike1MissionCompletedFlag", + "Roguelike1MissionFailedFlag" }); + return task.run(); +} + +bool asst::BattleTask::release_skill(const asst::Rect& rect) +{ + Ctrler.click(rect); + + ProcessTask task(*this, { "BattleReleaseSkill" }); return task.run(); } diff --git a/src/MeoAssistant/BattleTask.h b/src/MeoAssistant/BattleTask.h index d517ede2c3..d0bc66a045 100644 --- a/src/MeoAssistant/BattleTask.h +++ b/src/MeoAssistant/BattleTask.h @@ -17,8 +17,10 @@ namespace asst bool auto_battle(); bool speed_up(); bool wait_for_mission_completed(); + bool release_skill(const Rect& rect); std::vector m_home_cache; bool m_used_opers = false; + int m_pre_hp = 0; }; } diff --git a/src/MeoAssistant/Controller.cpp b/src/MeoAssistant/Controller.cpp index 7b41adede6..6aa4c11fc1 100644 --- a/src/MeoAssistant/Controller.cpp +++ b/src/MeoAssistant/Controller.cpp @@ -101,7 +101,7 @@ asst::Controller::~Controller() close(m_pipe_out[PIPE_READ]); close(m_pipe_out[PIPE_WRITE]); #endif - } +} bool asst::Controller::connect_adb(const std::string & address) { @@ -166,7 +166,9 @@ bool asst::Controller::connect_adb(const std::string & address) asst::Rect asst::Controller::shaped_correct(const Rect & rect) const { - if (rect.empty()) { + if (rect.empty() + || m_scale_size.first == 0 + || m_scale_size.second == 0) { return rect; } // 明日方舟在异形屏上,有的地方是按比例缩放的,有的地方又是直接位移。没法整,这里简单粗暴一点截一个长条 @@ -465,7 +467,7 @@ std::optional> asst::Controller::call_command(const s else { // failed to create child process return std::nullopt; - } +} #endif Log.trace("Call `", cmd, "` ret", exit_ret, ", output size:", pipe_data.size()); diff --git a/src/MeoAssistant/Linux编译教程.md b/src/MeoAssistant/Linux编译教程.md deleted file mode 100644 index a34aa18ede..0000000000 --- a/src/MeoAssistant/Linux编译教程.md +++ /dev/null @@ -1,3 +0,0 @@ -# Linux 编译教程 - -请跳转至 [新链接](../../docs/Linux编译教程.md)