From 8486b65a962eadbe31cdd989379685e9f488bb67 Mon Sep 17 00:00:00 2001 From: MistEO Date: Sun, 23 Jan 2022 17:51:09 +0800 Subject: [PATCH] =?UTF-8?q?feat.=E5=AE=8C=E6=88=90=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E9=87=8A=E6=94=BE=E6=8A=80=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/tasks.json | 149 ++++++++++++++++++++++- resource/template/BattleHpFlag.png | Bin 0 -> 1849 bytes resource/template/BattleHpFlag2.png | Bin 0 -> 1527 bytes resource/template/BattleSkillReady.png | Bin 0 -> 1545 bytes src/MeoAssistant/BattleImageAnalyzer.cpp | 101 ++++++++++++++- src/MeoAssistant/BattleImageAnalyzer.h | 12 +- src/MeoAssistant/BattleTask.cpp | 51 ++++++-- src/MeoAssistant/BattleTask.h | 2 + src/MeoAssistant/Controller.cpp | 8 +- src/MeoAssistant/Linux编译教程.md | 3 - 10 files changed, 306 insertions(+), 20 deletions(-) create mode 100644 resource/template/BattleHpFlag.png create mode 100644 resource/template/BattleHpFlag2.png create mode 100644 resource/template/BattleSkillReady.png delete mode 100644 src/MeoAssistant/Linux编译教程.md 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 0000000000000000000000000000000000000000..928ec85c6611e53da362ac76f051de20772955e5 GIT binary patch literal 1849 zcmV-92gdk`P)rs(Nt_m?NsJJv6euVmRa6ND^(jRN zi98^IL{Te2rG|&_LQqtdq!f@^0!5-mfGUL~P7_iinmCCa+Z)@vUfcUIb1x6GA8}l$ zleB-$Xs*tgbN=^y+dZ9gp z0Anc11w#0`O#NDtox1?AGf6BRsbi{h7jB>$fU23xgs;C#AQ0fCmwrY^M+X3Nb8{Rx z@NF(#y2OhwzCdSZC$?=bncBhUzQfSlZ(~^&FCTunY~TELkF#Mz zA8XgGEz1jq0<)=EP!5aj6<@)bQ7kK$p=$bl7VFjkkgIQ}5R4+TX(lGFFCl1dZf4)U zeanbTNI5!`4a2srWDZ0I z$>^1<=x`mmhIIf$#)gSreh1akvgS}=ZW5tgGKo&|P7t>$PW9L@UM9`8nK?H0ej3ZN zZku>!W`=Jbc$NjNcz?^cNG7@o&W_RcrymoTn?&^WZ8XLhjB-e|_Aqh(APb34Q8RXm zhSRT5HF}Qztpj}Ri-SZW5u{WUmn~3Q&&4qYqcyJUl1wJ~(N7OErfsfwKgn!!53%?D zOylX}1ZS@TAo?EOU<@DyAOy4bbThL1hX~;!^%S2x_C10#R}g@0+m#IXI|m-+9R<&I zv6Uj9&!M$ON9q`U>L=u#CY)4~`(Aw(d;UgwvR1{900f1q7#aaMxauBEArA=Z;&nWC z@F1%iRso_EI5!23F$Mt)4GnSR$PtWNz;mN`P8FUCpsm1?vRGv=#b+T>Uwp}+@|m(K z9Eq^Lb3OI-^&eX6spOPD&<3=>Curs1S}fy>_Qfm_q*M(tQ9w1G#Uj2 zvampCA&taflnv4@=GVVvDS@S@XNPNlA3F`P$4hHpO5%8)d*x}iqf$d zjYd}-c|$`(xi>`WI$8*fw8+-BED2QllsDJBx;Qcj;~|RL>gsBwEYI~r5s5^6@|#RN zCv;20m8uVDtFjy<9#$cPQogmn#8!@ARS*EXFpn@^nOxiysw^j52y`G!p(+M|(s`<~ z6XhI*i%qmr#A3dm(84&D@lVY{cRNJSR-U znO_qd{vDPu2I1ydjJ2>(*A77J#yQRm{f$#Yc}5=UW?*33Z4;Mp`0&pGn0#O->HeJn z=;B5u0p=22W#Vvlp4fXs7~y~TeSUE8j-m#zEQ{^iw*yc!dX~uiGypg5-G;VMsR zgX+{jm nXstgs^48WCYHOpU^Dh4dXK9VSwKU6a00000NkvXXu0mjf#MyE+ literal 0 HcmV?d00001 diff --git a/resource/template/BattleHpFlag2.png b/resource/template/BattleHpFlag2.png new file mode 100644 index 0000000000000000000000000000000000000000..b898cf012d4fdd7fcd23b80bf4cf179bb37ce312 GIT binary patch literal 1527 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 literal 0 HcmV?d00001 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)