diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index 0c3f91faaa..3f5c072ae1 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -29,8 +29,10 @@ struct OperatorRequirements // int elite = -1; // 精英化等级 // int level = -1; // 干员等级 // int skill_level = -1; // 技能等级 - int module = -1; // 模组编号 -1: 不切换模组 / 无要求, 0: 不使用模组, 1: 模组χ, 2: 模组γ, 3: 模组α, 4: 模组Δ // int potentiality = -1; // 潜能要求 + int module = -1; // 模组编号 -1: 不切换模组 / 无要求, 0: 不使用模组, 1: 模组χ, 2: 模组γ, 3: 模组α, 4: 模组Δ + + bool operator==(const OperatorRequirements& req) const { return module == req.module; } }; // 干员编队状态 @@ -50,7 +52,13 @@ struct OperUsage // 干员用法 SkillUsage skill_usage = SkillUsage::NotUse; int skill_times = 1; // 使用技能的次数,默认为 1,兼容曾经的作业 battle::OperatorRequirements requirements {}; // 练度需求 - OperStatus status = OperStatus::Unchecked; // 编队状态 + OperStatus status = OperStatus::Unchecked; // 编队状态, 可能有其他更好的位置存储 + + bool operator==(const OperUsage& other) const + { + return name == other.name && skill == other.skill && skill_usage == other.skill_usage && + skill_times == other.skill_times && requirements == other.requirements; + } }; enum class DeployDirection diff --git a/src/MaaCore/Task/Interface/CopilotTask.cpp b/src/MaaCore/Task/Interface/CopilotTask.cpp index 8c8bfc6131..2f263e4a4f 100644 --- a/src/MaaCore/Task/Interface/CopilotTask.cpp +++ b/src/MaaCore/Task/Interface/CopilotTask.cpp @@ -151,9 +151,10 @@ bool asst::CopilotTask::set_params(const json::value& params) size_t count = configs_cvt.size(); // 追加任务 m_subtasks.reserve(m_subtasks.size() * count); - auto raw_end = m_subtasks.end(); + // 保存原始大小 + size_t original_size = m_subtasks.size(); for (size_t i = 1; i < count; ++i) { - m_subtasks.insert(m_subtasks.end(), m_subtasks.begin(), raw_end); + m_subtasks.insert(m_subtasks.end(), m_subtasks.begin(), m_subtasks.begin() + original_size); } m_multi_copilot_plugin_ptr->set_multi_copilot_config(std::move(configs_cvt)); m_has_subtasks_duplicate = true; @@ -202,9 +203,9 @@ bool asst::CopilotTask::set_params(const json::value& params) // 追加 m_subtasks.reserve(m_subtasks.size() * loop_times); - auto raw_end = m_subtasks.end(); + size_t original_size = m_subtasks.size(); for (size_t i = 1; i < loop_times; ++i) { - m_subtasks.insert(m_subtasks.end(), m_subtasks.begin(), raw_end); + m_subtasks.insert(m_subtasks.end(), m_subtasks.begin(), m_subtasks.begin() + original_size); } m_has_subtasks_duplicate = true; } diff --git a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp index 18e6a37189..fa8778af77 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp @@ -58,6 +58,26 @@ bool asst::BattleFormationTask::_run() if (!parse_formation()) { return false; } + else if (compare_formation()) { + Log.info(__FUNCTION__, "| Formation is the same as last time, skip"); + for (auto& [name, opers] : m_formation | std::views::values | std::views::join) { + const auto& pair_it = + std::ranges::find_if(*m_opers_in_formation, [&](const auto& pair) { return pair.second == name; }); + if (pair_it == m_opers_in_formation->end()) { + continue; + } + + auto oper_it = + std::ranges::find_if(opers, [&](const battle::OperUsage& oper) { return oper.name == pair_it->first; }); + if (oper_it == opers.end()) { + Log.error(__FUNCTION__, "| Cannot find oper ", pair_it->first, " in m_formation"); + } + else { + oper_it->status = battle::OperStatus::Selected; // 更新编队情况 + } + } + return true; // 编队未变更,跳过 + } if (m_select_formation_index > 0 && !select_formation(m_select_formation_index, img)) { return false; @@ -182,19 +202,66 @@ bool asst::BattleFormationTask::_run() void asst::BattleFormationTask::formation_with_last_opers() { - std::vector opers; - for (auto& [role, oper_groups] : m_formation) { - for (auto& oper_group : oper_groups) { - if (oper_group.second.size() != 1) { - continue; - } - // 不支持干员组,以免选中练度更低的干员 - opers.emplace_back(&oper_group); - } + if (m_opers_in_formation->empty()) { + return; + } + std::unordered_map need_formation; + m_opers_in_formation->swap(need_formation); + + // 此时的oper_in_formation是根据上次编队结果复用的, 但是task此时已清空, 重新选一下 + const auto& opers_result = analyzer_opers(ctrler()->get_image()); + if (opers_result.empty()) { + return; } - if (need_exit() || !select_opers_in_cur_page(opers)) { - return; + // 展平的干员组 + std::unordered_map*> formation_view; + for (auto& group : m_formation | std::views::values | std::views::join) { + formation_view.emplace(group.first, &group.second); + } + const int delay = Task.get("BattleQuickFormationOCR")->post_delay; + for (auto it = need_formation.begin(); !need_exit() && it != need_formation.end();) { + const std::string& oper_name = it->first; + const std::string& group_name = it->second; + + const auto& oper_in_page_it = std::ranges::find_if(opers_result, [&](const QuickFormationOper& op) { + return !op.is_selected && op.text == oper_name; + }); // 编队页中的干员 + if (oper_in_page_it == opers_result.end()) [[unlikely]] { + Log.warn(__FUNCTION__, "| Oper", oper_name, "was selected last time, but not found in current page"); + ++it; + continue; // 该干员找不到, 一页只能放下10个干员, 可能被右侧挡住. 打回到正常编队逻辑 + } + + // 直接通过组名从展平的映射中查找 + auto group_it = formation_view.find(group_name); + if (group_it == formation_view.end()) { + LogError << __FUNCTION__ << "| Group" << group_name << " not found in formation"; + ++it; + continue; + } + + // 在找到的组中查找具体的干员 + auto oper_it = + std::ranges::find_if(*(group_it->second), [&](battle::OperUsage& op) { return op.name == oper_name; }); + if (oper_it == group_it->second->end()) { + LogError << __FUNCTION__ << "| Group" << group_name << ",Oper" << oper_name + << "was selected last time, but not found in current pair"; + ++it; + continue; // 很怪, 按理说不会找不到, 有组相同但是找不到干员 + } + + ctrler()->click(oper_in_page_it->flag_rect); + sleep(delay); + + oper_it->status = battle::OperStatus::Selected; + json::value info = basic_info_with_what("BattleFormationSelected"); + auto& details = info["details"]; + details["selected"] = oper_name; + details["group_name"] = group_name; + callback(AsstMsg::SubTaskExtraInfo, info); + m_opers_in_formation->emplace(oper_name, group_name); + it = need_formation.erase(it); } } @@ -496,7 +563,7 @@ bool asst::BattleFormationTask::select_opers_in_cur_page(const std::vectorpost_delay; + const int delay = Task.get("BattleQuickFormationOCR")->post_delay; battle::OperUsage* oper = nullptr; bool ret = true; for (const auto& res : @@ -624,7 +691,10 @@ bool asst::BattleFormationTask::parse_formation() groups = &SSSCopilot.get_data().groups; } + std::swap(m_formation, m_formation_last); m_formation.clear(); + m_opers_in_formation->clear(); + m_last_oper_name = std::string(); for (const auto& [name, opers_vec] : *groups) { if (opers_vec.empty()) { continue; @@ -646,12 +716,48 @@ bool asst::BattleFormationTask::parse_formation() // for unknown, will use { "BattleQuickFormationRole-All", "BattleQuickFormationRole-All-OCR" } m_formation[same_role ? role : battle::Role::Unknown].emplace_back(name, opers_vec); } - callback(AsstMsg::SubTaskExtraInfo, info); - m_opers_in_formation->clear(); return true; } +bool asst::BattleFormationTask::compare_formation() +{ + for (auto& [role, groups_] : m_formation) { + const auto& role_it = m_formation_last.find(role); + if (role_it == m_formation_last.cend()) { + continue; + } + + auto& last_groups = role_it->second; + for (auto& group : groups_) { + auto last_group_it = std::ranges::find_if(last_groups, [&](const OperGroup& g) { + return g.first == group.first && g.second == group.second; + }); + if (last_group_it == last_groups.end()) { // fallback to only opers equal + last_group_it = + std::ranges::find_if(last_groups, [&](const OperGroup& g) { return g.second == group.second; }); + } + if (last_group_it == last_groups.end()) { + continue; // not find the same group in last formation + } + + const auto& oper_last_it = std::ranges::find_if(last_group_it->second, [&](const battle::OperUsage& op) { + return op.status == battle ::OperStatus::Selected; + }); + if (oper_last_it == last_group_it->second.cend()) [[unlikely]] { + LogError << __FUNCTION__ << "| Group" << last_group_it->first + << "was selected last time, but no oper was selected"; + continue; // 旧编队中该干员组有干员被选中,但找不到被选中的干员 + } + m_opers_in_formation->emplace(oper_last_it->name, group.first); + last_groups.erase(last_group_it); // 移除已匹配的干员组 + } + } + + return static_cast(std::ranges::distance(m_formation | std::views::values | std::views::join)) == + m_opers_in_formation->size(); +} + bool asst::BattleFormationTask::select_formation(int select_index, const cv::Mat& img) { // 编队不会触发改名的区域有两组 diff --git a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h index 439a9a83a2..8c9c6c4031 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h @@ -89,6 +89,8 @@ protected: virtual bool _run() override; bool parse_formation(); + // 判断当前编队是否与上次相同, 同时为相同部分添加进已选中, return 是否完全相同 + bool compare_formation(); bool is_formation_valid(const cv::Mat& img) const; bool select_formation(int select_index, const cv::Mat& img); bool enter_selection_page(const cv::Mat& img = cv::Mat()); @@ -113,13 +115,14 @@ protected: std::vector analyzer_opers(const cv::Mat& image); - std::string m_stage_name; - std::unordered_map> m_formation; // 作业编队 + std::unordered_map> m_formation; // 作业编队 + std::unordered_map> m_formation_last; // 上次的编队 + // 编队中的干员名称-所属组名 std::shared_ptr> m_opers_in_formation = - std::make_shared>(); // 编队中的干员名称-所属组名 - bool m_add_trust = false; // 是否需要追加信赖干员 - bool m_ignore_requirements = false; // 是否跳过未满足的干员属性要求 - std::vector> m_user_additional; // 追加干员表,从头往后加 + std::make_shared>(); + bool m_add_trust = false; // 是否需要追加信赖干员 + bool m_ignore_requirements = false; // 是否跳过未满足的干员属性要求 + std::vector> m_user_additional; // 追加干员表,从头往后加 DataResource m_data_resource = DataResource::Copilot; std::vector m_additional; std::string m_last_oper_name;