diff --git a/3rdparty/include/Arknights-Tile-Pos/TileCalc2.hpp b/3rdparty/include/Arknights-Tile-Pos/TileCalc2.hpp index 3be61f8fe5b..f0f48a90dbe 100644 --- a/3rdparty/include/Arknights-Tile-Pos/TileCalc2.hpp +++ b/3rdparty/include/Arknights-Tile-Pos/TileCalc2.hpp @@ -39,8 +39,8 @@ inline matrix4x4 camera_matrix_from_trans( const vec3d& euler, double ratio, double fov_2_y = 20 * degree, - double far = 1000, - double near = 0.3) + double far_c = 1000, + double near_c = 0.3) { const double cos_y = std::cos(euler[0]); const double sin_y = std::sin(euler[0]); @@ -66,11 +66,11 @@ inline matrix4x4 camera_matrix_from_trans( 0, -sin_x, -cos_x, 0, // 0, 0, 0, 1, }; - const matrix4x4 proj = { + const matrix4x4 proj = matrix4x4 { // clang-format off ratio / tan_f, 0, 0, 0, 0, 1 / tan_f, 0, 0, - 0, 0, -(far + near) / (far - near), -(far * near * 2) / (far - near), + 0, 0, -(far_c + near_c) / (far_c - near_c), -(far_c * near_c * 2) / (far_c - near_c), 0, 0, -1, 0, // clang-format on }; @@ -78,8 +78,7 @@ inline matrix4x4 camera_matrix_from_trans( return proj * matrixX * matrixY * translate; } -inline cv::Point - world_to_screen(const Level& level, const vec3d& world_pos, bool side, const vec3d& offset = {}) +inline cv::Point world_to_screen(const Level& level, const vec3d& world_pos, bool side, const vec3d& offset = {}) { static constexpr double width = 1280; static constexpr double height = 720; @@ -107,12 +106,7 @@ inline vec3d get_tile_world_pos(const Level& level, int tile_y, int tile_x) }; } -inline auto get_tile_screen_pos( - const Level& level, - int tile_y, - int tile_x, - bool side = false, - const vec3d& offset = {}) +inline auto get_tile_screen_pos(const Level& level, int tile_y, int tile_x, bool side = false, const vec3d& offset = {}) { return world_to_screen(level, get_tile_world_pos(level, tile_y, tile_x), side, offset); } diff --git a/resource/tasks.json b/resource/tasks.json index 243d0104520..6b3f0c6c5ca 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -4627,29 +4627,12 @@ "BattleSkillReady": { "rectMove": [-28, -140, 64, 64] }, - "BattleSkillReadyOnClick": { - "roi": [748, 414, 210, 118], - "templThreshold": 0.7, - "action": "ClickSelf", - "rectMove": [0, -110, 90, 90], - "postDelay": 300, - "next": ["#self", "Stop"] - }, - "BattleSkillReadyOnClick-SquareMap": { - "baseTask": "BattleSkillReadyOnClick", - "template": "BattleSkillReadyOnClick-SquareMap.png" - }, - "BattleSkillStopOnClick": { - "baseTask": "BattleSkillReadyOnClick", - "template": "BattleSkillStopOnClick.png" - }, - "BattleSkillStopOnClick-MiddleMap": { - "baseTask": "BattleSkillReadyOnClick", - "template": "BattleSkillStopOnClick-MiddleMap.png" - }, - "BattleSkillStopOnClick-SquareMap": { - "baseTask": "BattleSkillReadyOnClick", - "template": "BattleSkillStopOnClick-SquareMap.png" + "BattleSkillReadyOnClick-TopView": { + "template": [ + "BattleSkillReadyOnClick-TopView.png", + "BattleSkillStopOnClick-TopView.png" + ], + "templThreshold": [ 0.8, 0.8 ] }, "BattleUseOper": { "algorithm": "JustReturn", diff --git a/resource/template/BattleSkillReady.png b/resource/template/BattleSkillReady.png deleted file mode 100644 index 46f95b7d047..00000000000 Binary files a/resource/template/BattleSkillReady.png and /dev/null differ diff --git a/resource/template/BattleSkillReadyOnClick-SquareMap.png b/resource/template/BattleSkillReadyOnClick-SquareMap.png deleted file mode 100644 index 05ad1fa9c40..00000000000 Binary files a/resource/template/BattleSkillReadyOnClick-SquareMap.png and /dev/null differ diff --git a/resource/template/BattleSkillReadyOnClick-TopView.png b/resource/template/BattleSkillReadyOnClick-TopView.png new file mode 100644 index 00000000000..942ea7cac8c Binary files /dev/null and b/resource/template/BattleSkillReadyOnClick-TopView.png differ diff --git a/resource/template/BattleSkillReadyOnClick.png b/resource/template/BattleSkillReadyOnClick.png deleted file mode 100644 index 0aab1851680..00000000000 Binary files a/resource/template/BattleSkillReadyOnClick.png and /dev/null differ diff --git a/resource/template/BattleSkillStopOnClick-MiddleMap.png b/resource/template/BattleSkillStopOnClick-MiddleMap.png deleted file mode 100644 index e88755b44a8..00000000000 Binary files a/resource/template/BattleSkillStopOnClick-MiddleMap.png and /dev/null differ diff --git a/resource/template/BattleSkillStopOnClick-SquareMap.png b/resource/template/BattleSkillStopOnClick-SquareMap.png deleted file mode 100644 index d4aa7166aab..00000000000 Binary files a/resource/template/BattleSkillStopOnClick-SquareMap.png and /dev/null differ diff --git a/resource/template/BattleSkillStopOnClick-TopView.png b/resource/template/BattleSkillStopOnClick-TopView.png new file mode 100644 index 00000000000..692193b1ce9 Binary files /dev/null and b/resource/template/BattleSkillStopOnClick-TopView.png differ diff --git a/resource/template/BattleSkillStopOnClick.png b/resource/template/BattleSkillStopOnClick.png deleted file mode 100644 index 5d6645b98f2..00000000000 Binary files a/resource/template/BattleSkillStopOnClick.png and /dev/null differ diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index c5a62cf07ea..9c4407f450c 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -3,6 +3,7 @@ #include #include +#include "Config/GeneralConfig.h" #include "Config/Miscellaneous/AvatarCacheManager.h" #include "Config/Miscellaneous/BattleDataConfig.h" #include "Config/TaskData.h" @@ -11,16 +12,19 @@ #include "Utils/ImageIo.hpp" #include "Utils/Logger.hpp" #include "Utils/NoWarningCV.h" +#include "Utils/Time.hpp" #include "Vision/Battle/BattlefieldClassifier.h" #include "Vision/Battle/BattlefieldMatcher.h" #include "Vision/BestMatcher.h" #include "Vision/Matcher.h" #include "Vision/RegionOCRer.h" +#include "Arknights-Tile-Pos/TileCalc2.hpp" + using namespace asst::battle; -asst::BattleHelper::BattleHelper(Assistant* inst) - : m_inst_helper(inst) +asst::BattleHelper::BattleHelper(Assistant* inst) : + m_inst_helper(inst) { } @@ -54,10 +58,7 @@ void asst::BattleHelper::clear() m_used_tiles.clear(); } -bool asst::BattleHelper::calc_tiles_info( - const std::string& stage_name, - double shift_x, - double shift_y) +bool asst::BattleHelper::calc_tiles_info(const std::string& stage_name, double shift_x, double shift_y) { LogTraceFunction; @@ -99,7 +100,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable, b LogTraceFunction; if (init) { - AvatarCache.remove_avatars(Role::Drone);// 移除小龙等不同技能很像的召唤物,防止错误识别 + AvatarCache.remove_avatars(Role::Drone); // 移除小龙等不同技能很像的召唤物,防止错误识别 wait_until_start(false); } @@ -157,14 +158,12 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable, b Log.trace("start matching cooling", oper.index); static const auto cooling_threshold = Task.get("BattleAvatarCoolingData")->templ_thresholds.front(); - static const auto cooling_mask_range = - Task.get("BattleAvatarCoolingData")->mask_ranges; + static const auto cooling_mask_range = Task.get("BattleAvatarCoolingData")->mask_ranges; avatar_analyzer.set_threshold(cooling_threshold); avatar_analyzer.set_mask_ranges(cooling_mask_range, true, true); } else { - static const auto threshold = - Task.get("BattleAvatarData")->templ_thresholds.front(); + static const auto threshold = Task.get("BattleAvatarData")->templ_thresholds.front(); static const auto drone_threshold = Task.get("BattleDroneAvatarData")->templ_thresholds.front(); avatar_analyzer.set_threshold(oper.role == Role::Drone ? drone_threshold : threshold); @@ -208,8 +207,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable, b } } - if (ranges::count_if(unknown_opers, [](const DeploymentOper& it) { return !it.cooling; }) > 0 - || init) { + if (ranges::count_if(unknown_opers, [](const DeploymentOper& it) { return !it.cooling; }) > 0 || init) { // 一个都没匹配上的,挨个点开来看一下 LogTraceScope("rec unknown opers"); @@ -285,6 +283,38 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable, b return check_in_battle(image); } +// if side = true, get top view of the selected operator, tile size is 5x5 +cv::Mat asst::BattleHelper::get_top_view(const cv::Mat& cam_img, bool side) +{ + if (!side) { + return cv::Mat {}; // TODO + } + + const std::vector world_points { + { -2.5, -2.5 }, + { 2.5, -2.5 }, + { -2.5, 2.5 }, + { 2.5, 2.5 }, + }; + const std::vector dst_points { + { 0, 0 }, + { 500, 0 }, + { 0, 500 }, + { 500, 500 }, + }; + std::vector screen_points; + for (const auto& point : world_points) { + cv::Vec3d temp { point.x, -point.y, -0.3967874050140381 }; + auto screen_pt = Map::TileCalc2::world_to_screen(m_map_data, temp, true); + screen_points.push_back(screen_pt); + } + + const auto xform = cv::getPerspectiveTransform(screen_points, dst_points); + cv::Mat result; + cv::warpPerspective(cam_img, result, xform, cv::Size(500, 500)); + return result; +} + bool asst::BattleHelper::update_kills(const cv::Mat& reusable) { cv::Mat image = reusable.empty() ? m_inst_helper.ctrler()->get_image() : reusable; @@ -314,10 +344,7 @@ bool asst::BattleHelper::update_cost(const cv::Mat& reusable) return true; } -bool asst::BattleHelper::deploy_oper( - const std::string& name, - const Point& loc, - DeployDirection direction) +bool asst::BattleHelper::deploy_oper(const std::string& name, const Point& loc, DeployDirection direction) { LogTraceFunction; @@ -337,9 +364,8 @@ bool asst::BattleHelper::deploy_oper( } Point target_point = target_iter->second.pos; - int dist = static_cast(Point::distance( - target_point, - { oper_rect.x + oper_rect.width / 2, oper_rect.y + oper_rect.height / 2 })); + int dist = static_cast( + Point::distance(target_point, { oper_rect.x + oper_rect.width / 2, oper_rect.y + oper_rect.height / 2 })); // 1000 是随便取的一个系数,把整数的 pre_delay 转成小数用的 int duration = static_cast(dist / 1000.0 * swipe_oper_task_ptr->pre_delay); @@ -347,9 +373,8 @@ bool asst::BattleHelper::deploy_oper( if (int min_duration = swipe_oper_task_ptr->special_params.at(4); duration < min_duration) { duration = min_duration; } - bool deploy_with_pause = ControlFeat::support( - m_inst_helper.ctrler()->support_features(), - ControlFeat::SWIPE_WITH_PAUSE); + bool deploy_with_pause = + ControlFeat::support(m_inst_helper.ctrler()->support_features(), ControlFeat::SWIPE_WITH_PAUSE); Point oper_point(oper_rect.x + oper_rect.width / 2, oper_rect.y + oper_rect.height / 2); m_inst_helper.ctrler()->swipe( oper_point, @@ -447,9 +472,7 @@ bool asst::BattleHelper::retreat_oper(const Point& loc, bool manually) m_used_tiles.erase(loc); if (manually) { - std::erase_if(m_battlefield_opers, [&loc](const auto& pair) -> bool { - return pair.second == loc; - }); + std::erase_if(m_battlefield_opers, [&loc](const auto& pair) -> bool { return pair.second == loc; }); } return true; } @@ -611,10 +634,7 @@ bool asst::BattleHelper::use_all_ready_skill(const cv::Mat& reusable) return used; } -bool asst::BattleHelper::check_and_use_skill( - const std::string& name, - bool& has_error, - const cv::Mat& reusable) +bool asst::BattleHelper::check_and_use_skill(const std::string& name, bool& has_error, const cv::Mat& reusable) { auto oper_iter = m_battlefield_opers.find(name); if (oper_iter == m_battlefield_opers.cend()) { @@ -624,10 +644,7 @@ bool asst::BattleHelper::check_and_use_skill( return check_and_use_skill(oper_iter->second, has_error, reusable); } -bool asst::BattleHelper::check_and_use_skill( - const Point& loc, - bool& has_error, - const cv::Mat& reusable) +bool asst::BattleHelper::check_and_use_skill(const Point& loc, bool& has_error, const cv::Mat& reusable) { cv::Mat image = reusable.empty() ? m_inst_helper.ctrler()->get_image() : reusable; BattlefieldClassifier skill_analyzer(image); @@ -658,14 +675,7 @@ void asst::BattleHelper::save_map(const cv::Mat& image) auto draw = image.clone(); for (const auto& [loc, info] : m_normal_tile_info) { cv::circle(draw, cv::Point(info.pos.x, info.pos.y), 5, cv::Scalar(0, 255, 0), -1); - cv::putText( - draw, - loc.to_string(), - cv::Point(info.pos.x - 30, info.pos.y), - 1, - 1.2, - cv::Scalar(0, 0, 255), - 2); + cv::putText(draw, loc.to_string(), cv::Point(info.pos.x - 30, info.pos.y), 1, 1.2, cv::Scalar(0, 0, 255), 2); } std::string suffix; @@ -732,9 +742,8 @@ bool asst::BattleHelper::click_oper_on_battlefield(const Point& loc) bool asst::BattleHelper::click_retreat() { LogTraceFunction; - bool deploy_with_pause = ControlFeat::support( - m_inst_helper.ctrler()->support_features(), - ControlFeat::SWIPE_WITH_PAUSE); + bool deploy_with_pause = + ControlFeat::support(m_inst_helper.ctrler()->support_features(), ControlFeat::SWIPE_WITH_PAUSE); if (deploy_with_pause) { ProcessTask(this_task(), { "BattlePause" }).run(); @@ -748,39 +757,48 @@ bool asst::BattleHelper::click_retreat() return ret; } -// TODO: use m_skill_button_pos bool asst::BattleHelper::click_skill(bool keep_waiting) { LogTraceFunction; - bool deploy_with_pause = ControlFeat::support( - m_inst_helper.ctrler()->support_features(), - ControlFeat::SWIPE_WITH_PAUSE); + bool deploy_with_pause = + ControlFeat::support(m_inst_helper.ctrler()->support_features(), ControlFeat::SWIPE_WITH_PAUSE); + bool pausing = false; if (!keep_waiting && deploy_with_pause) { - ProcessTask(this_task(), { "BattlePause" }).run(); + pausing = ProcessTask(this_task(), { "BattlePause" }).run(); + } + + cv::Mat top_view; + for (int retry = 0; retry < (keep_waiting ? 1000 : 5); ++retry) { + top_view = get_top_view(m_inst_helper.ctrler()->get_image(), true); + Matcher skill_analyzer { top_view }; + skill_analyzer.set_task_info("BattleSkillReadyOnClick-TopView"); + skill_analyzer.set_roi({ 250, 250, 250, 250 }); + if (skill_analyzer.analyze()) { + m_inst_helper.ctrler()->click(m_skill_button_pos); + if (pausing) { + ProcessTask(this_task(), { "BattlePauseCancel" }).run(); + } + return true; + } + m_inst_helper.sleep(Config.get_options().task_delay); } - ProcessTask skill_task( - this_task(), - { "BattleSkillReadyOnClick", - "BattleSkillReadyOnClick-SquareMap", - "BattleSkillStopOnClick", - "BattleSkillStopOnClick-MiddleMap", - "BattleSkillStopOnClick-SquareMap" }); - skill_task.set_task_delay(0); - if (keep_waiting) { - return skill_task.set_retry_times(1000).run(); + // this means false positive in skill ready detection + +#ifdef ASST_DEBUG + if (!top_view.empty()) { + using namespace asst::utils::path_literals; + asst::imwrite( + "debug"_p / "skill"_p / asst::utils::path(m_stage_name + '_' + utils::get_time_filestem() + ".png"), + top_view); } - else { - bool ret = skill_task.set_retry_times(5).run(); - if (!ret) { - cancel_oper_selection(); - } - if (deploy_with_pause) { - ProcessTask(this_task(), { "BattlePauseCancel" }).run(); - } - return ret; +#endif + cancel_oper_selection(); + if (pausing) { + ProcessTask(this_task(), { "BattlePauseCancel" }).run(); } + return false; } bool asst::BattleHelper::cancel_oper_selection() @@ -828,8 +846,7 @@ void asst::BattleHelper::fix_swipe_out_of_limit( }; // 旋转后偏移值会不够,计算补偿比例 - double adjust_more = - std::get<0>(adjust_scale) * direct.x + std::get<1>(adjust_scale) * direct.y; + double adjust_more = std::get<0>(adjust_scale) * direct.x + std::get<1>(adjust_scale) * direct.y; Point adjust = { static_cast(std::get<0>(adjust_scale) / adjust_more * distance), @@ -843,14 +860,7 @@ void asst::BattleHelper::fix_swipe_out_of_limit( }; } - Log.info( - __FUNCTION__, - "swipe end_point out of limit, start:", - p1, - ", end:", - p2, - ", adjust:", - adjust); + Log.info(__FUNCTION__, "swipe end_point out of limit, start:", p1, ", end:", p2, ", adjust:", adjust); p1 += adjust; p2 += adjust; } @@ -905,14 +915,11 @@ std::string asst::BattleHelper::analyze_detail_page_oper_name(const cv::Mat& ima return BattleData.is_name_invalid(det_name) ? std::string() : det_name; } -std::optional - asst::BattleHelper::get_oper_rect_on_deployment(const std::string& name) const +std::optional asst::BattleHelper::get_oper_rect_on_deployment(const std::string& name) const { LogTraceFunction; - auto oper_iter = ranges::find_if(m_cur_deployment_opers, [&](const auto& oper) { - return oper.name == name; - }); + auto oper_iter = ranges::find_if(m_cur_deployment_opers, [&](const auto& oper) { return oper.name == name; }); if (oper_iter == m_cur_deployment_opers.end()) { Log.error("No oper", name); return std::nullopt; diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h index f597e302456..b39e17125f0 100644 --- a/src/MaaCore/Task/BattleHelper.h +++ b/src/MaaCore/Task/BattleHelper.h @@ -39,6 +39,8 @@ namespace asst bool update_kills(const cv::Mat& reusable = cv::Mat()); bool update_cost(const cv::Mat& reusable = cv::Mat()); + cv::Mat get_top_view(const cv::Mat& cam_img, bool side = true); + bool deploy_oper(const std::string& name, const Point& loc, battle::DeployDirection direction); bool retreat_oper(const std::string& name); bool retreat_oper(const Point& loc, bool manually = true);