#include "BattleProcessTask.h" #include #include #include "Controller.h" #include "Resource.h" #include "TaskData.h" #include "Logger.hpp" #include "ProcessTask.h" #include "MatchImageAnalyzer.h" #include "OcrImageAnalyzer.h" #include "BattleImageAnalyzer.h" void asst::BattleProcessTask::set_stage_name(std::string name) { m_stage_name = std::move(name); } bool asst::BattleProcessTask::_run() { bool ret = get_stage_info(); if (!ret) { return false; } while (!analyze_opers_preview()) { std::this_thread::yield(); } for (const auto& action : m_actions_group.actions) { do_action(action); } return true; } bool asst::BattleProcessTask::get_stage_info() { const auto& tile = Resrc.tile(); m_normal_tile_info = tile.calc(m_stage_name, false); m_side_tile_info = tile.calc(m_stage_name, true); if (m_side_tile_info.empty() || m_normal_tile_info.empty()) { return false; } const auto& battle = Resrc.battle(); bool contains = battle.contains_actions(m_stage_name); if (!contains) { return false; } m_actions_group = battle.get_actions(m_stage_name); return true; } bool asst::BattleProcessTask::analyze_opers_preview() { BattleImageAnalyzer oper_analyzer; oper_analyzer.set_target(BattleImageAnalyzer::Target::Oper); while (true) { oper_analyzer.set_image(m_ctrler->get_image()); if (oper_analyzer.analyze()) { break; } std::this_thread::yield(); } //#ifdef ASST_DEBUG auto draw = m_ctrler->get_image(); for (const auto& [loc, info] : m_normal_tile_info) { std::string text = "( " + std::to_string(loc.x) + ", " + std::to_string(loc.y) + " )"; cv::putText(draw, text, cv::Point(info.pos.x - 30, info.pos.y), 1, 1.2, cv::Scalar(0, 0, 255), 2); } cv::imwrite(utils::utf8_to_gbk(m_stage_name) + ".png", draw); //#endif // 干员头像出来之后,还要过 2 秒左右才可以点击,这里要加个延时 sleep(Task.get("BattleWaitingToLoad")->rear_delay); battle_pause(); auto opers = oper_analyzer.get_opers(); for (size_t i = 0; i != opers.size(); ++i) { const auto& cur_oper = oper_analyzer.get_opers(); size_t offset = opers.size() > cur_oper.size() ? opers.size() - cur_oper.size() : 0; m_ctrler->click(cur_oper.at(i - offset).rect); sleep(Task.get("BattleUseOper")->pre_delay); auto image = m_ctrler->get_image(); OcrImageAnalyzer name_analyzer(image); name_analyzer.set_task_info("BattleOperName"); name_analyzer.set_replace( std::dynamic_pointer_cast( Task.get("Roguelike1RecruitData")) ->replace_map); std::string oper_name = "Unknown"; if (name_analyzer.analyze()) { name_analyzer.sort_result_by_score(); oper_name = name_analyzer.get_result().front().text; } opers.at(i).name = oper_name; bool not_found = true; // 找出这个干员是哪个组里的,以及他的技能用法等 for (const auto& [group_name, deploy_opers] : m_actions_group.groups) { auto iter = std::find_if(deploy_opers.cbegin(), deploy_opers.cend(), [&](const BattleDeployOper& deploy) -> bool { return deploy.name == oper_name; }); if (iter != deploy_opers.cend()) { m_group_to_oper_mapping.emplace(group_name, *iter); not_found = false; break; } } // 没找到,可能是召唤物等新出现的 if (not_found) { m_group_to_oper_mapping.emplace(oper_name, BattleDeployOper{ oper_name }); } m_cur_opers_info.emplace(std::move(oper_name), std::move(opers.at(i))); // 干员特别多的时候,任意干员被点开,都会导致下方的干员图标被裁剪和移动。所以这里需要重新识别一下 oper_analyzer.set_image(image); oper_analyzer.analyze(); } battle_pause(); cancel_selection(); return true; } bool asst::BattleProcessTask::update_opers_info(const cv::Mat& image) { BattleImageAnalyzer analyzer(image); analyzer.set_target(BattleImageAnalyzer::Target::Oper); if (!analyzer.analyze()) { return false; } const auto& cur_opers_info = analyzer.get_opers(); // 除非主动使用,不然可用干员数任何情况下都不会减少 // 主动使用会 earse, 所以少了就是识别错了 if (cur_opers_info.size() < m_cur_opers_info.size()) { Log.error(__FUNCTION__, "Decrease in staff, Just return"); return false; } decltype(m_cur_opers_info) pre_opers_info; m_cur_opers_info.swap(pre_opers_info); const int size_change = static_cast(cur_opers_info.size()) - static_cast(pre_opers_info.size()); for (const auto& cur_oper : cur_opers_info) { if (cur_oper.cooling) { continue; } // 该干员在上一帧中可能的位置。需要考虑召唤者退场&可用召唤物消失的情况,所以 lhs-1 rhs+1 int left_index = std::max(0, static_cast(cur_oper.index) - size_change - 1); int right_index = static_cast(cur_oper.index + 1); std::vector ranged_iters; // 找出该干员可能对应的之前的谁 for (auto iter = pre_opers_info.cbegin(); iter != pre_opers_info.cend(); ++iter) { int pre_index = static_cast(iter->second.index); if (left_index <= pre_index && pre_index <= right_index) { ranged_iters.emplace_back(iter); } } // 干员也可能是撤退下来的,把所有已使用的都拿出来比较下 for (auto iter = m_all_opers_info.cbegin(); iter != m_all_opers_info.cend(); ++iter) { const std::string& key = iter->first; if (pre_opers_info.find(key) == pre_opers_info.cend()) { ranged_iters.emplace_back(iter); } } std::string oper_name = "Unknown"; MatchRect matched_result; decltype(ranged_iters)::value_type matched_iter; MatchImageAnalyzer avatar_analyzer(cur_oper.avatar); avatar_analyzer.set_task_info("BattleAvatarData"); // 遍历比较,得分最高的那个就说明是对应的那个 for (const auto& iter : ranged_iters) { avatar_analyzer.set_templ(iter->second.avatar); if (!avatar_analyzer.analyze()) { continue; } if (matched_result.score < avatar_analyzer.get_result().score) { matched_result = avatar_analyzer.get_result(); matched_iter = iter; } } // 一个都没匹配上,考虑是新增的召唤物,或者别的东西,点开来看一下 if (matched_result.score == 0) { battle_pause(); m_ctrler->click(cur_oper.rect); sleep(Task.get("BattleUseOper")->pre_delay); OcrImageAnalyzer name_analyzer(m_ctrler->get_image()); name_analyzer.set_task_info("BattleOperName"); name_analyzer.set_replace( std::dynamic_pointer_cast( Task.get("Roguelike1RecruitData")) ->replace_map); if (name_analyzer.analyze()) { name_analyzer.sort_result_by_score(); oper_name = name_analyzer.get_result().front().text; } m_group_to_oper_mapping[oper_name] = BattleDeployOper{ oper_name }; battle_pause(); cancel_selection(); } else { oper_name = matched_iter->first; m_used_opers.erase(oper_name); } auto temp_oper = cur_oper; temp_oper.name = oper_name; // 保存当前干员信息 m_all_opers_info[oper_name] = temp_oper; m_cur_opers_info.emplace(oper_name, std::move(temp_oper)); } return true; } bool asst::BattleProcessTask::do_action(const BattleAction& action) { if (!wait_condition(action)) { return false; } if (action.pre_delay > 0) { sleep_with_possible_skill(action.pre_delay); // 等待之后画面可能会变化,再调用一次等待条件更新干员信息 wait_condition(action); } bool ret = false; switch (action.type) { case BattleActionType::Deploy: ret = oper_deploy(action); break; case BattleActionType::Retreat: ret = oper_retreat(action); break; case BattleActionType::UseSkill: ret = use_skill(action); break; case BattleActionType::SwitchSpeed: ret = battle_speedup(); break; case BattleActionType::BulletTime: break; case BattleActionType::SkillUsage: { auto& oper_info = m_group_to_oper_mapping[action.group_name]; oper_info.skill_usage = action.modify_usage; m_used_opers[oper_info.name].info.skill_usage = action.modify_usage; return true; } break; } sleep_with_possible_skill(action.rear_delay); return ret; } bool asst::BattleProcessTask::wait_condition(const BattleAction& action) { while (m_kills < action.kills) { const auto& image = m_ctrler->get_image(); BattleImageAnalyzer analyzer(image); analyzer.set_target(BattleImageAnalyzer::Target::Kills); if (analyzer.analyze()) { m_kills = analyzer.get_kills(); if (m_kills >= action.kills) { break; } } try_possible_skill(image); std::this_thread::yield(); } // 部署干员还有额外等待费用够或 CD 转好 if (action.type == BattleActionType::Deploy) { const std::string& name = m_group_to_oper_mapping[action.group_name].name; while (true) { const auto& image = m_ctrler->get_image(); update_opers_info(image); if (auto iter = m_cur_opers_info.find(name); iter != m_cur_opers_info.cend() && iter->second.available) { break; } try_possible_skill(image); std::this_thread::yield(); }; } return true; } bool asst::BattleProcessTask::oper_deploy(const BattleAction& action) { const auto use_oper_task_ptr = Task.get("BattleUseOper"); const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper"); const auto& oper_info = m_group_to_oper_mapping[action.group_name]; auto iter = m_cur_opers_info.find(oper_info.name); // 点击干员 Rect oper_rect = iter->second.rect; m_ctrler->click(oper_rect); sleep(use_oper_task_ptr->pre_delay); // 拖动到场上 Point placed_point = m_side_tile_info[action.location].pos; Rect placed_rect{ placed_point.x ,placed_point.y, 1, 1 }; m_ctrler->swipe(oper_rect, placed_rect, swipe_oper_task_ptr->pre_delay); sleep(use_oper_task_ptr->rear_delay); // 拖动干员朝向 if (action.direction != BattleDeployDirection::None) { static const std::unordered_map DirectionMapping = { { BattleDeployDirection::Right, Point(1, 0)}, { BattleDeployDirection::Down, Point(0, 1)}, { BattleDeployDirection::Left, Point(-1, 0)}, { BattleDeployDirection::Up, Point(0, -1)}, { BattleDeployDirection::None, Point(0, 0)}, }; // 计算往哪边拖动 Point direction = DirectionMapping.at(action.direction); // 将方向转换为实际的 swipe end 坐标点 Point end_point = placed_point; constexpr int coeff = 500; end_point.x += direction.x * coeff; end_point.y += direction.y * coeff; end_point.x = std::max(0, end_point.x); end_point.x = std::min(end_point.x, WindowWidthDefault); end_point.y = std::max(0, end_point.y); end_point.y = std::min(end_point.y, WindowHeightDefault); m_ctrler->swipe(placed_point, end_point, swipe_oper_task_ptr->rear_delay); } m_used_opers[iter->first] = BattleDeployInfo{ action.location, m_normal_tile_info[action.location].pos, oper_info }; m_cur_opers_info.erase(iter); //sleep(use_oper_task_ptr->pre_delay); return true; } bool asst::BattleProcessTask::oper_retreat(const BattleAction& action) { const std::string& name = m_group_to_oper_mapping[action.group_name].name; auto iter = m_used_opers.find(name); if (iter == m_used_opers.cend()) { Log.error(name, " not used"); return false; } Point pos = iter->second.pos; m_ctrler->click(pos); sleep(Task.get("BattleUseOper")->pre_delay); m_used_opers.erase(name); return ProcessTask(*this, { "BattleOperRetreat" }).run(); } bool asst::BattleProcessTask::use_skill(const BattleAction& action) { const std::string& name = m_group_to_oper_mapping[action.group_name].name; auto iter = m_used_opers.find(name); if (iter == m_used_opers.cend()) { Log.error(name, " not used"); return false; } Point pos = iter->second.pos; m_ctrler->click(pos); sleep(Task.get("BattleUseOper")->pre_delay); return ProcessTask(*this, { "BattleSkillReadyOnClick" }) .set_task_delay(0) .set_retry_times(10000) .run(); } bool asst::BattleProcessTask::try_possible_skill(const cv::Mat& image) { static const Rect& skill_roi_move = Task.get("BattleAutoSkillFlag")->rect_move; MatchImageAnalyzer analyzer(image); analyzer.set_task_info("BattleAutoSkillFlag"); bool used = false; for (auto& [name, info] : m_used_opers) { if (info.info.skill_usage != BattleSkillUsage::Possibly && info.info.skill_usage != BattleSkillUsage::Once) { continue; } const Rect roi = Rect{ info.pos.x, info.pos.y, 0, 0 }.move(skill_roi_move); analyzer.set_roi(roi); if (!analyzer.analyze()) { continue; } m_ctrler->click(info.pos); used |= ProcessTask(*this, { "BattleSkillReadyOnClick" }).run(); if (info.info.skill_usage == BattleSkillUsage::Once) { info.info.skill_usage = BattleSkillUsage::OnceUsed; } } return used; } void asst::BattleProcessTask::sleep_with_possible_skill(unsigned millisecond) { if (need_exit()) { return; } if (millisecond == 0) { return; } auto start = std::chrono::steady_clock::now(); long long duration = 0; Log.trace("ready to sleep_with_possible_skill", millisecond); while (!need_exit() && duration < millisecond) { duration = std::chrono::duration_cast( std::chrono::steady_clock::now() - start).count(); try_possible_skill(m_ctrler->get_image()); std::this_thread::yield(); } Log.trace("end of sleep_with_possible_skill", millisecond); } bool asst::BattleProcessTask::battle_pause() { return ProcessTask(*this, { "BattlePause" }).run(); } bool asst::BattleProcessTask::battle_speedup() { return ProcessTask(*this, { "BattleSpeedUp" }).run(); } bool asst::BattleProcessTask::cancel_selection() { return ProcessTask(*this, { "BattleCancelSelection" }).run(); }