feat: 自动战斗多作业支持保存干员组内编入的干员 (#14095)

* chore: 加个存储先

* feat: 与旧编队比较

* feat: 快速编队

* fix: 计数

* fix: 缓存了旧的组名

* perf: 快速退出

* fix: 跳过编队后修改编队状态

* fix: 完事

* fix: err

* fix: 顺手一起修了
This commit is contained in:
Status102
2025-09-15 01:27:51 +08:00
committed by GitHub
parent bec0325236
commit e2b209e723
4 changed files with 144 additions and 26 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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<OperGroup*> 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<std::string, std::string> 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<std::string, std::vector<asst::battle::OperUsage>*> 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::vector<OperG
m_last_oper_name = opers_result.back().text;
}
int delay = Task.get("BattleQuickFormationOCR")->post_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<size_t>(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)
{
// 编队不会触发改名的区域有两组

View File

@@ -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<QuickFormationOper> analyzer_opers(const cv::Mat& image);
std::string m_stage_name;
std::unordered_map<battle::Role, std::vector<OperGroup>> m_formation; // 作业编队
std::unordered_map<battle::Role, std::vector<OperGroup>> m_formation; // 作业编队
std::unordered_map<battle::Role, std::vector<OperGroup>> m_formation_last; // 上次的编队
// 编队中的干员名称-所属组名
std::shared_ptr<std::unordered_map<std::string, std::string>> m_opers_in_formation =
std::make_shared<std::unordered_map<std::string, std::string>>(); // 编队中的干员名称-所属组名
bool m_add_trust = false; // 是否需要追加信赖干员
bool m_ignore_requirements = false; // 是否跳过未满足的干员属性要求
std::vector<std::pair<std::string, int>> m_user_additional; // 追加干员表,从头往后加
std::make_shared<std::unordered_map<std::string, std::string>>();
bool m_add_trust = false; // 是否需要追加信赖干员
bool m_ignore_requirements = false; // 是否跳过未满足的干员属性要求
std::vector<std::pair<std::string, int>> m_user_additional; // 追加干员表,从头往后加
DataResource m_data_resource = DataResource::Copilot;
std::vector<AdditionalFormation> m_additional;
std::string m_last_oper_name;