diff --git a/docs/3.7-保全派驻协议.md b/docs/3.7-保全派驻协议.md index b57dbaf255..76b2a3afe5 100644 --- a/docs/3.7-保全派驻协议.md +++ b/docs/3.7-保全派驻协议.md @@ -42,7 +42,7 @@ "无需增调干员", // 不招人 "重整导能组件", // 支持装备名,全写一起.jpg "反制导能组件", - "战备激活阀", // 关卡中途的可选装备,也也放这里。需要配合 actions 中 type: GetDrops 使用 + "战备激活阀", // 关卡中途的可选装备,也也放这里 "改派发讯器", ], "blacklist": [ // 黑名单,可选。在 drops 里不会选这些人。 @@ -107,14 +107,6 @@ "type": "CheckIfStartOver", // 新 type,检查干员在不在,不在就退出重开 "name": "棘刺" }, - { - "type": "GetDrops", // 检查获取战斗中途的战利品,将按照 drops 字段的排序进行拿去 - // 若 drops 字段为空,或 drops 中的物品一个都没识别到,将会选择放弃战利品 - // 若执行动作时未识别到战利品获取,将会直接跳过该 action - "kills": 20, - "pre_delay": 3000 // 小技巧:一般来说都是打死了某个怪才会出,所以可以基于击杀数来触发 - // 但是从击杀数达到到选择界面跳出来可能有个延迟,所以建议价格 pre_delay - }, { "name": "桃金娘", "location": [ diff --git a/resource/tasks.json b/resource/tasks.json index 7467a3bfe1..56a2a837ad 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -9401,6 +9401,7 @@ }, "SSSDrawCard": { "action": "ClickSelf", + "templThreshold": 0.98, "roi": [ 1151, 319, @@ -9455,7 +9456,7 @@ 900, 112 ], - "postDelay": 5000, + "postDelay": 3000, "next": [ "SSSStartFighting2", "SSSDropRecruitmentFlag" @@ -9504,7 +9505,7 @@ "postDelay": 1000 }, "SSSHalfTimeDropsBegin": { - "algorithm": "JustReturn", + "template": "SSSHalfTimeDropsConfirm.png", "action": "DoNothing", "next": [ "SSSHalfTimeDrops", @@ -9526,29 +9527,33 @@ ] }, "SSSHalfTimeDropsConfirm": { - "algorithm": "OcrDetect", "action": "ClickSelf", - "text": [ - "确认" - ], "roi": [ - 1073, - 281, - 155, - 151 + 1034, + 283, + 148, + 148 ] }, "SSSHalfTimeDropsCancel": { - "algorithm": "OcrDetect", "action": "ClickSelf", - "text": [ - "放弃" - ], "roi": [ - 51, - 251, - 146, - 199 + 100, + 284, + 145, + 145 + ], + "next": [ + "SSSHalfTimeDropsCancelConfirm" + ] + }, + "SSSHalfTimeDropsCancelConfirm": { + "action": "ClickSelf", + "roi": [ + 716, + 410, + 254, + 163 ] }, "ClickCornerUntilStartButton": { diff --git a/resource/template/SSSHalfTimeDropsCancel.png b/resource/template/SSSHalfTimeDropsCancel.png new file mode 100644 index 0000000000..8ffc1adafc Binary files /dev/null and b/resource/template/SSSHalfTimeDropsCancel.png differ diff --git a/resource/template/SSSHalfTimeDropsCancelConfirm.png b/resource/template/SSSHalfTimeDropsCancelConfirm.png new file mode 100644 index 0000000000..5009758ae4 Binary files /dev/null and b/resource/template/SSSHalfTimeDropsCancelConfirm.png differ diff --git a/resource/template/SSSHalfTimeDropsConfirm.png b/resource/template/SSSHalfTimeDropsConfirm.png new file mode 100644 index 0000000000..a46ac97c3f Binary files /dev/null and b/resource/template/SSSHalfTimeDropsConfirm.png differ diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index 394ef2f6ad..a5ed808b88 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -95,18 +95,6 @@ namespace asst::battle return Role::Unknown; } - struct DeploymentOper - { - size_t index = 0; - Role role = Role::Unknown; - int cost = 0; - bool available = false; - bool cooling = false; - Rect rect; - cv::Mat avatar; - std::string name; - }; - enum class OperPosition { None, @@ -123,6 +111,38 @@ namespace asst::battle All = 3 }; + inline static LocationType get_role_usual_location(const Role& role) + { + switch (role) { + case Role::Warrior: + case Role::Pioneer: + case Role::Tank: + case Role::Special: + case Role::Drone: + return LocationType::Melee; + case Role::Medic: + case Role::Sniper: + case Role::Caster: + case Role::Support: + return LocationType::Ranged; + default: + return LocationType::None; + } + } + + struct DeploymentOper + { + size_t index = 0; + Role role = Role::Unknown; + int cost = 0; + bool available = false; + bool cooling = false; + Rect rect; + cv::Mat avatar; + std::string name; + LocationType location_type = LocationType::None; + bool is_unusual_location = false; // 地面辅助,高台先锋等 + }; struct OperProps { std::string name; @@ -157,7 +177,6 @@ namespace asst::battle /* for SSS */ DrawCard, // “调配干员” CheckIfStartOver, // 检查如果没有某干员则退出重开 - GetDrops, // 识别并获取战斗中途的掉落物 }; struct Action diff --git a/src/MaaCore/Config/Miscellaneous/AvatarCacheManager.cpp b/src/MaaCore/Config/Miscellaneous/AvatarCacheManager.cpp index c6f2736d84..5b6a92d9fd 100644 --- a/src/MaaCore/Config/Miscellaneous/AvatarCacheManager.cpp +++ b/src/MaaCore/Config/Miscellaneous/AvatarCacheManager.cpp @@ -16,7 +16,7 @@ bool asst::AvatarCacheManager::load(const std::filesystem::path& path) return true; } - const auto& [_, _, w, h] = Task.get("BattleOperAvatar")->rect_move; + const auto& [_1, _2, w, h] = Task.get("BattleOperAvatar")->rect_move; for (const auto& entry : std::filesystem::directory_iterator(path)) { if (!entry.is_regular_file()) { diff --git a/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp b/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp index 6063d6ecd3..54e1d14001 100644 --- a/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp @@ -155,14 +155,6 @@ std::vector asst::CopilotConfig::parse_actions(co { "CHECKIFSTARTOVER", ActionType::CheckIfStartOver }, { "checkifstartover", ActionType::CheckIfStartOver }, { "检查重开", ActionType::CheckIfStartOver }, - - { "GetDrops", ActionType::GetDrops }, - { "Getdrops", ActionType::GetDrops }, - { "GETDROPS", ActionType::GetDrops }, - { "getdrops", ActionType::GetDrops }, - { "获取战利品", ActionType::GetDrops }, - { "获取掉落", ActionType::GetDrops }, - { "获取装备", ActionType::GetDrops }, }; std::string type_str = action_info.get("type", "Deploy"); @@ -171,7 +163,8 @@ std::vector asst::CopilotConfig::parse_actions(co action.type = iter->second; } else { - action.type = ActionType::Deploy; + Log.warn("Unknown action type:", type_str); + continue; } action.kills = action_info.get("kills", 0); action.cost_changes = action_info.get("cost_changes", 0); diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index ecded7e252..f6a847e95a 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -115,6 +115,12 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) m_battlefield_opers.erase(iter); }; + auto set_oper_name = [&](DeploymentOper& oper, const std::string& name) { + oper.name = name; + oper.location_type = BattleData.get_location_type(name); + oper.is_unusual_location = battle::get_role_usual_location(oper.role) == oper.location_type; + }; + auto cur_opers = oper_analyzer.get_opers(); std::vector unknown_opers; @@ -137,7 +143,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) avatar_analyzer.append_templ(name, avatar); } if (avatar_analyzer.analyze()) { - oper.name = avatar_analyzer.get_result_name(); + set_oper_name(oper, avatar_analyzer.get_result_name()); m_cur_deployment_opers.insert_or_assign(oper.name, oper); remove_cooling_from_battlefield(oper); } @@ -151,7 +157,8 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) } } - if (!unknown_opers.empty() || init) { + if (!unknown_opers.empty() || init) + { // 一个都没匹配上的,挨个点开来看一下 LogTraceScope("rec unknown opers"); @@ -166,11 +173,19 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) std::this_thread::yield(); } while (!m_inst_helper.need_exit()); + if (!check_in_battle(image)) { + return false; + } + for (auto& oper : unknown_opers) { LogTraceScope("rec unknown oper: " + std::to_string(oper.index)); click_oper_on_deployment(oper.rect); cv::Mat name_image = m_inst_helper.ctrler()->get_image(); + if (!check_in_battle(name_image)) { + return false; + } + auto analyze = [&](OcrImageAnalyzer& name_analyzer) { name_analyzer.set_image(name_image); name_analyzer.set_task_info(oper_name_ocr_task_name()); @@ -202,7 +217,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) Log.error("name is empty"); continue; } - oper.name = name; + set_oper_name(oper, name); remove_cooling_from_battlefield(oper); if (oper.cooling) { @@ -229,9 +244,8 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) if (init) { update_kills(image); } - check_in_battle(image); - return true; + return check_in_battle(image); } bool asst::BattleHelper::update_kills(const cv::Mat& reusable) @@ -610,9 +624,7 @@ bool asst::BattleHelper::move_camera(const std::pair& delta) m_camera_shift.second += delta.second; calc_tiles_info(m_stage_name, -m_camera_shift.first, m_camera_shift.second); - update_deployment(true); - - return true; + return update_deployment(true); } std::optional asst::BattleHelper::get_oper_rect_on_deployment(const std::string& name) const diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h index 9c85e66b1c..69e0645575 100644 --- a/src/MaaCore/Task/BattleHelper.h +++ b/src/MaaCore/Task/BattleHelper.h @@ -45,7 +45,7 @@ namespace asst bool use_skill(const std::string& name, bool keep_waiting = true); bool use_skill(const Point& loc, bool keep_waiting = true); bool check_pause_button(const cv::Mat& reusable = cv::Mat()); - bool check_in_battle(const cv::Mat& reusable = cv::Mat(), bool weak = false); + bool check_in_battle(const cv::Mat& reusable = cv::Mat(), bool weak = true); virtual bool wait_until_start(bool weak = true); bool wait_until_end(bool weak = true); bool use_all_ready_skill(const cv::Mat& reusable = cv::Mat()); diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 616ff6f7f8..0a095430d0 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -135,7 +135,7 @@ bool asst::BattleProcessTask::to_group() bool asst::BattleProcessTask::do_action(size_t action_index) { LogTraceFunction; - + const auto& action = get_combat_data().actions.at(action_index); notify_action(action); @@ -144,7 +144,7 @@ bool asst::BattleProcessTask::do_action(size_t action_index) return false; } if (action.pre_delay > 0) { - sleep_and_do_not_urgent(action.pre_delay); + sleep_and_do_strategy(action.pre_delay); // 等待之后画面可能会变化,更新下干员信息 update_deployment(); } @@ -200,7 +200,7 @@ bool asst::BattleProcessTask::do_action(size_t action_index) break; } - sleep_and_do_not_urgent(action.post_delay); + sleep_and_do_strategy(action.post_delay); return ret; } @@ -229,7 +229,6 @@ void asst::BattleProcessTask::notify_action(const battle::copilot::Action& actio { ActionType::MoveCamera, "MoveCamera" }, { ActionType::DrawCard, "DrawCard" }, { ActionType::CheckIfStartOver, "CheckIfStartOver" }, - { ActionType::GetDrops, "GetDrops" }, }; json::value info = basic_info_with_what("CopilotAction"); @@ -305,15 +304,14 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) if (action.cooling >= 0) { update_image_if_empty(); while (!need_exit() && m_in_battle) { - update_deployment(false, image); + if (!update_deployment(false, image)) { + return false; + } size_t cooling_count = ranges::count_if(m_cur_deployment_opers | views::values, [](const auto& oper) -> bool { return oper.cooling; }); if (cooling_count == action.cooling) { break; } - if (!check_in_battle(image)) { - return false; - } do_strategy_and_update_image(); } } @@ -323,14 +321,13 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) const std::string& name = get_name_from_group(action.name); update_image_if_empty(); while (!need_exit() && m_in_battle) { - update_deployment(false, image); + if (!update_deployment(false, image)) { + return false; + } if (auto iter = m_cur_deployment_opers.find(name); iter != m_cur_deployment_opers.cend() && iter->second.available) { break; } - if (!check_in_battle(image)) { - return false; - } do_strategy_and_update_image(); } } @@ -368,7 +365,7 @@ bool asst::BattleProcessTask::enter_bullet_time_for_next_action(size_t next_inde return ret; } -void asst::BattleProcessTask::sleep_and_do_not_urgent(unsigned millisecond) +void asst::BattleProcessTask::sleep_and_do_strategy(unsigned millisecond) { LogTraceScope(__FUNCTION__ + std::string(" ms: ") + std::to_string(millisecond)); diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h index 7f0c89a9f1..46e062866f 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h @@ -33,7 +33,7 @@ namespace asst void notify_action(const battle::copilot::Action& action); bool wait_condition(const battle::copilot::Action& action); bool enter_bullet_time_for_next_action(size_t next_index, const Point& location, const std::string& name); - void sleep_and_do_not_urgent(unsigned millisecond); + void sleep_and_do_strategy(unsigned millisecond); battle::copilot::CombatData m_combat_data; std::unordered_map m_oper_in_group; diff --git a/src/MaaCore/Task/ProcessTask.cpp b/src/MaaCore/Task/ProcessTask.cpp index 6cd6cef9ba..ad310a359f 100644 --- a/src/MaaCore/Task/ProcessTask.cpp +++ b/src/MaaCore/Task/ProcessTask.cpp @@ -79,6 +79,12 @@ ProcessTask& asst::ProcessTask::set_post_delay(std::string name, int delay) return *this; } +ProcessTask& asst::ProcessTask::set_reusable_image(const cv::Mat& reusable) +{ + m_reusable = reusable; + return *this; +} + bool ProcessTask::_run() { LogTraceFunction; @@ -112,7 +118,8 @@ bool ProcessTask::_run() m_cur_task_ptr = front_task_ptr; } else { - const auto image = ctrler()->get_image(); + cv::Mat image = m_reusable.empty() ? ctrler()->get_image() : m_reusable; + m_reusable = cv::Mat(); ProcessTaskImageAnalyzer analyzer(image, m_cur_task_name_list, m_inst); if (!analyzer.analyze()) { diff --git a/src/MaaCore/Task/ProcessTask.h b/src/MaaCore/Task/ProcessTask.h index 18e4406b6e..440723aa56 100644 --- a/src/MaaCore/Task/ProcessTask.h +++ b/src/MaaCore/Task/ProcessTask.h @@ -2,6 +2,7 @@ #include "AbstractTask.h" #include "Common/AsstTypes.h" +#include "Utils/NoWarningCVMat.h" namespace asst { @@ -34,6 +35,7 @@ namespace asst ProcessTask& set_tasks(std::vector tasks_name) noexcept; ProcessTask& set_times_limit(std::string name, int limit, TimesLimitType type = TimesLimitType::Pre); ProcessTask& set_post_delay(std::string name, int delay); + ProcessTask& set_reusable_image(const cv::Mat& reusable); const std::string& get_last_task_name() const noexcept { return m_last_task_name; } @@ -59,5 +61,6 @@ namespace asst std::unordered_map m_exec_times; static constexpr int TaskDelayUnsetted = -1; int m_task_delay = TaskDelayUnsetted; + cv::Mat m_reusable; }; } diff --git a/src/MaaCore/Task/SSS/SSSBattleProcessTask.cpp b/src/MaaCore/Task/SSS/SSSBattleProcessTask.cpp index 84bc7c9bec..0760da873b 100644 --- a/src/MaaCore/Task/SSS/SSSBattleProcessTask.cpp +++ b/src/MaaCore/Task/SSS/SSSBattleProcessTask.cpp @@ -48,8 +48,6 @@ bool asst::SSSBattleProcessTask::do_derived_action(size_t action_index) return draw_card(); case battle::copilot::ActionType::CheckIfStartOver: return check_if_start_over(action); - case battle::copilot::ActionType::GetDrops: - return get_drops(); default: Log.error("unknown action type", static_cast(action.type)); return false; @@ -59,10 +57,39 @@ bool asst::SSSBattleProcessTask::do_derived_action(size_t action_index) bool asst::SSSBattleProcessTask::do_strategic_action(const cv::Mat& reusable) { LogTraceFunction; - cv::Mat image = reusable.empty() ? ctrler()->get_image() : reusable; - update_deployment(false, image); - if (!m_in_battle) { + + if (check_and_do_strategy(image)) { + return true; + } + + if (check_and_get_drops(image)) { + return true; + } + + if (use_all_ready_skill(image)) { + return true; + } + + if (m_sss_combat_data.draw_as_possible && draw_card(false, image)) { + return true; + } + + return true; +} + +bool asst::SSSBattleProcessTask::wait_until_start(bool weak) +{ + LogTraceFunction; + + return ProcessTask(*this, { "SSSFightDirectly" }).set_retry_times(300).run() && + BattleProcessTask::wait_until_start(weak); +} + +bool asst::SSSBattleProcessTask::check_and_do_strategy(const cv::Mat& reusable) +{ + cv::Mat image = reusable.empty() ? ctrler()->get_image() : reusable; + if (!update_deployment(false, image)) { return false; } @@ -72,7 +99,7 @@ bool asst::SSSBattleProcessTask::do_strategic_action(const cv::Mat& reusable) if (m_all_cores.contains(name)) { exist_core.emplace(name, oper); } - else if (!m_all_action_opers.contains(oper.name)) { + else if (oper.is_unusual_location && !m_all_action_opers.contains(oper.name)) { tool_men.emplace_back(oper); // 工具人的技能一概好了就用 m_skill_usage.try_emplace(name, SkillUsage::Possibly); @@ -124,21 +151,7 @@ bool asst::SSSBattleProcessTask::do_strategic_action(const cv::Mat& reusable) } } - if (m_sss_combat_data.draw_as_possible) { - draw_card(false); - } - - use_all_ready_skill(image); - - return true; -} - -bool asst::SSSBattleProcessTask::wait_until_start(bool weak) -{ - LogTraceFunction; - - return ProcessTask(*this, { "SSSFightDirectly" }).set_retry_times(300).run() && - BattleProcessTask::wait_until_start(weak); + return false; } bool asst::SSSBattleProcessTask::check_if_start_over(const battle::copilot::Action& action) @@ -172,29 +185,39 @@ bool asst::SSSBattleProcessTask::check_if_start_over(const battle::copilot::Acti return true; } -bool asst::SSSBattleProcessTask::draw_card(bool with_retry) +bool asst::SSSBattleProcessTask::draw_card(bool with_retry, const cv::Mat& reusable) { - LogTraceFunction; + cv::Mat image = reusable.empty() ? ctrler()->get_image() : reusable; ProcessTask task(*this, { "SSSDrawCard" }); if (!with_retry) { - task.set_retry_times(0); + task.set_task_delay(0).set_retry_times(0); } + task.set_reusable_image(image); return task.run(); } -bool asst::SSSBattleProcessTask::get_drops() +bool asst::SSSBattleProcessTask::check_and_get_drops(const cv::Mat& reusable) { - LogTraceFunction; + cv::Mat image = reusable.empty() ? ctrler()->get_image() : reusable; + if (!ProcessTask(*this, { "SSSHalfTimeDropsBegin" }) + .set_reusable_image(image) + .set_times_limit("SSSHalfTimeDropsBegin", 0) + .set_task_delay(0) + .set_retry_times(0) + .run()) { + return false; + } const auto& drops = m_sss_combat_data.order_of_drops; - - // 不一定就有掉落界面,有可能宝箱怪压根没被打掉。所以不重试 + std::string task_name; if (drops.empty()) { - return ProcessTask(*this, { inst_string() + "@SSSHalfTimeDropsCancel" }).set_retry_times(0).run(); + task_name = inst_string() + "@SSSHalfTimeDropsCancel"; } else { Task.get(inst_string() + "@SSSHalfTimeDrops")->text = { drops }; - return ProcessTask(*this, { inst_string() + "@SSSHalfTimeDropsBegin" }).set_retry_times(0).run(); + task_name = inst_string() + "@SSSHalfTimeDropsBegin"; } + Log.info("Get drops", drops); + return ProcessTask(*this, { task_name }).set_reusable_image(image).run(); } diff --git a/src/MaaCore/Task/SSS/SSSBattleProcessTask.h b/src/MaaCore/Task/SSS/SSSBattleProcessTask.h index 42b8bf1173..7936e42cba 100644 --- a/src/MaaCore/Task/SSS/SSSBattleProcessTask.h +++ b/src/MaaCore/Task/SSS/SSSBattleProcessTask.h @@ -1,5 +1,5 @@ #pragma once -#include "../Miscellaneous/BattleProcessTask.h" +#include "Task/Miscellaneous/BattleProcessTask.h" namespace asst { @@ -19,9 +19,10 @@ namespace asst virtual bool need_to_wait_until_end() const noexcept override { return true; } virtual bool wait_until_start(bool weak = true) override; + bool check_and_do_strategy(const cv::Mat& reusable = cv::Mat()); bool check_if_start_over(const battle::copilot::Action& action); - bool draw_card(bool with_retry = true); - bool get_drops(); + bool draw_card(bool with_retry = true, const cv::Mat& reusable = cv::Mat()); + bool check_and_get_drops(const cv::Mat& reusable = cv::Mat()); battle::sss::CombatData m_sss_combat_data; std::unordered_set m_all_cores;