From 5de39a0ae55ca7145a7699c5d03f83bf620d2f42 Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 21 Dec 2022 00:54:28 +0800 Subject: [PATCH] =?UTF-8?q?refator:=20=E5=AE=8C=E6=88=90=E8=82=89=E9=B8=BD?= =?UTF-8?q?=E6=88=98=E6=96=97=E4=BB=BB=E5=8A=A1=E6=95=B4=E4=BD=93=E9=87=8D?= =?UTF-8?q?=E6=9E=84=EF=BC=88=E8=BF=98=E6=B2=A1=E6=B5=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/tasks.json | 4 +- src/MaaCore/Common/AsstBattleDef.h | 7 +- .../Roguelike/RoguelikeCopilotConfig.cpp | 5 - src/MaaCore/InstHelper.cpp | 5 + src/MaaCore/InstHelper.h | 1 + src/MaaCore/Task/AbstractTask.h | 1 + src/MaaCore/Task/AbstractTaskPlugin.h | 1 - src/MaaCore/Task/BattleHelper.cpp | 37 +- src/MaaCore/Task/BattleHelper.h | 14 +- src/MaaCore/Task/Interface/RoguelikeTask.cpp | 1 + .../Task/Miscellaneous/BattleProcessTask.cpp | 6 +- .../Task/Miscellaneous/BattleProcessTask.h | 2 +- .../Roguelike/RoguelikeBattleTaskPlugin.cpp | 1156 ++++++----------- .../Roguelike/RoguelikeBattleTaskPlugin.h | 167 ++- .../Miscellaneous/BattleImageAnalyzer.h | 2 - 15 files changed, 510 insertions(+), 899 deletions(-) diff --git a/resource/tasks.json b/resource/tasks.json index 2e607cb294..f445189be3 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -9112,7 +9112,7 @@ 204 ] }, - "RoguelikeWaitBattleStart": { + "RoguelikeWaitForStartButtonClicked": { "Doc": "点了开始行动之后,可能会网络不好一直loading,用这个任务等一会", "template": "Roguelike@QuickFormation.png", "action": "DoNothing", @@ -9123,7 +9123,7 @@ 146 ], "next": [ - "RoguelikeWaitBattleStart" + "RoguelikeWaitForStartButtonClicked" ] }, "RoguelikeRecruitSupportOcr": { diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index 1d9f29f72d..a2ed15e78a 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -96,14 +96,14 @@ namespace asst::battle struct DeploymentOper { - int cost = 0; + 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; - size_t index = 0; - bool cooling = false; }; enum class OperPosition @@ -197,7 +197,6 @@ namespace asst::battle std::vector replacement_home; std::unordered_set blacklist_location; std::unordered_map force_deploy_direction; - std::vector key_kills; std::array role_order = {}; bool use_dice_stage = true; int stop_deploy_blocking_num = INT_MAX; diff --git a/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp b/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp index 1bb9555843..9196ba2907 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp +++ b/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp @@ -57,11 +57,6 @@ bool asst::RoguelikeCopilotConfig::parse(const json::value& json) data.blacklist_location.emplace(point[0].as_integer(), point[1].as_integer()); } } - if (auto opt = stage_info.find("key_kills")) { - for (const auto& key_kill : opt.value()) { - data.key_kills.emplace_back(static_cast(key_kill)); - } - } data.use_dice_stage = !stage_info.get("not_use_dice", false); if (auto opt = stage_info.find("force_air_defense_when_deploy_blocking_num")) { diff --git a/src/MaaCore/InstHelper.cpp b/src/MaaCore/InstHelper.cpp index 9c0440e580..253dd60012 100644 --- a/src/MaaCore/InstHelper.cpp +++ b/src/MaaCore/InstHelper.cpp @@ -42,3 +42,8 @@ bool asst::InstHelper::sleep(unsigned millisecond) const return !need_exit(); } + +asst::Assistant* asst::InstHelper::inst() noexcept +{ + return m_inst; +} diff --git a/src/MaaCore/InstHelper.h b/src/MaaCore/InstHelper.h index ea7031fc67..f75e1c6176 100644 --- a/src/MaaCore/InstHelper.h +++ b/src/MaaCore/InstHelper.h @@ -21,6 +21,7 @@ namespace asst std::shared_ptr status() const; bool need_exit() const; bool sleep(unsigned millisecond) const; + Assistant* inst() noexcept; InstHelper& operator=(const InstHelper&) = default; InstHelper& operator=(InstHelper&&) noexcept = default; diff --git a/src/MaaCore/Task/AbstractTask.h b/src/MaaCore/Task/AbstractTask.h index 301dd05dd5..e806a2da60 100644 --- a/src/MaaCore/Task/AbstractTask.h +++ b/src/MaaCore/Task/AbstractTask.h @@ -29,6 +29,7 @@ namespace asst AbstractTask(const AbstractTask&) = default; AbstractTask(AbstractTask&&) noexcept = default; virtual ~AbstractTask() noexcept = default; + using InstHelper::inst; virtual bool run(); diff --git a/src/MaaCore/Task/AbstractTaskPlugin.h b/src/MaaCore/Task/AbstractTaskPlugin.h index 4cf42304e5..cd061ce620 100644 --- a/src/MaaCore/Task/AbstractTaskPlugin.h +++ b/src/MaaCore/Task/AbstractTaskPlugin.h @@ -23,7 +23,6 @@ namespace asst virtual bool verify(AsstMsg msg, const json::value& details) const = 0; std::strong_ordering operator<=>(const AbstractTaskPlugin& rhs) const; - bool operator==(const AbstractTaskPlugin& rhs) const; protected: diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index 5eb2fa3633..6d319840fb 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -34,18 +34,12 @@ bool asst::BattleHelper::calc_tiles_info(const std::string& stage_name) { LogTraceFunction; - auto normal_temp = Tile.calc(stage_name, false); - if (normal_temp.empty()) { + if (!Tile.contains(stage_name)) { return false; } - auto side_temp = Tile.calc(stage_name, true); - if (side_temp.empty()) { - return false; - } - - m_normal_tile_info = std::move(normal_temp); - m_side_tile_info = std::move(side_temp); + m_normal_tile_info = Tile.calc(stage_name, false); + m_side_tile_info = Tile.calc(stage_name, true); return true; } @@ -54,17 +48,17 @@ bool asst::BattleHelper::pause() { LogTraceFunction; - return ProcessTask(*dynamic_cast(this), { "BattlePause" }).run(); + return ProcessTask(this_task(), { "BattlePause" }).run(); } bool asst::BattleHelper::speed_up() { LogTraceFunction; - return ProcessTask(*dynamic_cast(this), { "BattleSpeedUp" }).run(); + return ProcessTask(this_task(), { "BattleSpeedUp" }).run(); } -bool asst::BattleHelper::analyze_deployment_opers(bool init) +bool asst::BattleHelper::update_deployment(bool init) { LogTraceFunction; @@ -433,19 +427,29 @@ bool asst::BattleHelper::click_retreat() { LogTraceFunction; - return ProcessTask(*dynamic_cast(this), { "BattleOperRetreatJustClick" }).run(); + return ProcessTask(this_task(), { "BattleOperRetreatJustClick" }).run(); } bool asst::BattleHelper::click_skill() { LogTraceFunction; - return ProcessTask(*dynamic_cast(this), { "BattleSkillReadyOnClick", "BattleSkillStopOnClick" }) + return ProcessTask(this_task(), { "BattleSkillReadyOnClick", "BattleSkillStopOnClick" }) .set_task_delay(0) .set_retry_times(1000) .run(); } +bool asst::BattleHelper::cancel_oper_selection() +{ + return ProcessTask(this_task(), { "BattleCancelSelection" }).run(); +} + +bool asst::BattleHelper::is_name_invaild(const std::string& name) +{ + return BattleData.get_location_type(name) == battle::LocationType::Invalid; +} + std::optional asst::BattleHelper::get_oper_rect_on_deployment(const std::string& name) const { LogTraceFunction; @@ -458,3 +462,8 @@ std::optional asst::BattleHelper::get_oper_rect_on_deployment(const return oper_iter->second.rect; } + +asst::AbstractTask& asst::BattleHelper::this_task() +{ + return *dynamic_cast(this); +} diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h index 0be032c59c..28e1cf78f6 100644 --- a/src/MaaCore/Task/BattleHelper.h +++ b/src/MaaCore/Task/BattleHelper.h @@ -20,12 +20,13 @@ namespace asst BattleHelper(Assistant* inst); virtual bool set_stage_name(const std::string& name); + bool calc_tiles_info(const std::string& stage_name); bool pause(); bool speed_up(); - bool analyze_deployment_opers(bool init = false); + bool update_deployment(bool init = false); bool deploy_oper(const std::string& name, const Point& loc, battle::DeployDirection direction); bool retreat_oper(const std::string& name); @@ -46,10 +47,12 @@ namespace asst bool click_oper_on_battlefiled(const Point& loc); bool click_retreat(); // 这个是不带识别的,直接点 bool click_skill(); // 这个是带识别的,转好了才点 + bool cancel_oper_selection(); + + bool is_name_invaild(const std::string& name); std::optional get_oper_rect_on_deployment(const std::string& name) const; - InstHelper m_inst_helper; std::string m_stage_name; std::unordered_map m_side_tile_info; std::unordered_map m_normal_tile_info; @@ -64,5 +67,10 @@ namespace asst std::map m_battlefield_opers; std::map m_used_tiles; + + private: + virtual AbstractTask& this_task(); + + InstHelper m_inst_helper; }; -} +} // namespace asst diff --git a/src/MaaCore/Task/Interface/RoguelikeTask.cpp b/src/MaaCore/Task/Interface/RoguelikeTask.cpp index 8ac63665f5..d8d96a081b 100644 --- a/src/MaaCore/Task/Interface/RoguelikeTask.cpp +++ b/src/MaaCore/Task/Interface/RoguelikeTask.cpp @@ -27,6 +27,7 @@ asst::RoguelikeTask::RoguelikeTask(const AsstCallback& callback, Assistant* inst m_custom_start_task_ptr = m_roguelike_task_ptr->register_plugin(); m_battle_task_ptr = m_roguelike_task_ptr->register_plugin(); + m_battle_task_ptr->set_retry_times(0).set_ignore_error(true); m_recruit_task_ptr = m_roguelike_task_ptr->register_plugin(); m_recruit_task_ptr->set_retry_times(2).set_ignore_error(true); m_skill_task_ptr = m_roguelike_task_ptr->register_plugin(); diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 410d874b04..10a3d081b8 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -35,7 +35,7 @@ bool asst::BattleProcessTask::_run() return false; } - analyze_deployment_opers(true); + update_deployment(true); for (size_t i = 0; i < m_combat_data.actions.size() && !need_exit(); ++i) { do_action(i); @@ -71,7 +71,7 @@ bool asst::BattleProcessTask::do_action(size_t action_index) if (action.pre_delay > 0) { sleep_with_use_ready_skill(action.pre_delay); // 等待之后画面可能会变化,更新下干员信息 - analyze_deployment_opers(); + update_deployment(); } bool ret = false; @@ -226,7 +226,7 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) if (!m_in_bullet_time && action.type == ActionType::Deploy) { const std::string& name = m_oper_in_group[action.group_name]; while (!need_exit()) { - analyze_deployment_opers(); + update_deployment(); if (auto iter = m_cur_deployment_opers.find(name); iter != m_cur_deployment_opers.cend() && iter->second.available) { break; diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h index 387dacc8b3..ca36fa9917 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h @@ -9,7 +9,7 @@ namespace asst { - class BattleProcessTask : public AbstractTask, public BattleHelper + class BattleProcessTask : public AbstractTask, private BattleHelper { public: BattleProcessTask(const AsstCallback& callback, Assistant* inst, std::string_view task_chain); diff --git a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp index dc6ea602dc..f4e2e03e07 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp @@ -53,34 +53,24 @@ bool asst::RoguelikeBattleTaskPlugin::verify(AsstMsg msg, const json::value& det } } -void asst::RoguelikeBattleTaskPlugin::set_stage_name(std::string stage) -{ - m_stage_name = std::move(stage); -} - bool asst::RoguelikeBattleTaskPlugin::_run() { using namespace std::chrono_literals; + LogTraceFunction; - bool gotten_info = get_stage_info(); - if (!gotten_info) { - return true; - } - if (!wait_start()) { + if (!calc_stage_info()) { return false; } - speed_up(); + update_deployment(true); bool timeout = false; int not_in_battle_count = 0; auto start_time = std::chrono::steady_clock::now(); while (!need_exit()) { // 不在战斗场景,且已使用过了干员,说明已经打完了,就结束循环 - if (!auto_battle() && m_opers_used) { - if (++not_in_battle_count > 5) { - break; - } + if (!auto_battle() && !m_first_deploy && ++not_in_battle_count > 5) { + break; } else { not_in_battle_count = 0; @@ -98,10 +88,8 @@ bool asst::RoguelikeBattleTaskPlugin::_run() not_in_battle_count = 0; start_time = std::chrono::steady_clock::now(); while (!need_exit()) { - if (!auto_battle()) { - if (++not_in_battle_count > 5) { - break; - } + if (!auto_battle() && ++not_in_battle_count > 5) { + break; } else { not_in_battle_count = 0; @@ -109,39 +97,35 @@ bool asst::RoguelikeBattleTaskPlugin::_run() if (std::chrono::steady_clock::now() - start_time > 2min) { Log.info("Timeout again, abandon!"); abandon(); - break; + return false; } } } - - clear(); - return true; } -void asst::RoguelikeBattleTaskPlugin::wait_for_start() +void asst::RoguelikeBattleTaskPlugin::wait_for_start_button_clicked() { - ProcessTask(*this, { "RoguelikeWaitBattleStart" }).set_task_delay(0).set_retry_times(0).run(); + ProcessTask(*this, { "RoguelikeWaitForStartButtonClicked" }).set_task_delay(0).set_retry_times(0).run(); } -bool asst::RoguelikeBattleTaskPlugin::get_stage_info() +bool asst::RoguelikeBattleTaskPlugin::calc_stage_info() { LogTraceFunction; - wait_for_start(); + wait_for_start_button_clicked(); + clear(); bool calced = false; const auto stage_name_task_ptr = Task.get("BattleStageName"); sleep(stage_name_task_ptr->pre_delay); - constexpr int StageNameRetryTimes = 50; + constexpr int StageNameRetryTimes = 100; for (int i = 0; i != StageNameRetryTimes; ++i) { if (need_exit()) { return false; } - std::this_thread::yield(); - OcrWithPreprocessImageAnalyzer name_analyzer(ctrler()->get_image()); name_analyzer.set_task_info(stage_name_task_ptr); if (!name_analyzer.analyze()) { @@ -160,21 +144,20 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() for (const std::string& code : RoguelikeStageCode) { stage_key.code = code; - auto side_info = Tile.calc(stage_key, true); - if (side_info.empty()) { + if (!Tile.contains(stage_key)) { continue; } - m_stage_name = text; - - m_side_tile_info = std::move(side_info); - m_normal_tile_info = Tile.calc(stage_key, false); calced = true; + m_stage_name = text; + m_normal_tile_info = Tile.calc(stage_key, false); + m_side_tile_info = Tile.calc(stage_key, true); break; } if (calced) { break; } + std::this_thread::yield(); } if (!calced) { @@ -193,13 +176,14 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() Log.error("No replacement homes point:", invalid_homes_pos); } Log.info("replacement home:", homes_pos | views::transform(&Point::to_string)); + + m_allow_to_use_dice = opt->use_dice_stage; m_blacklist_location = opt->blacklist_location; - m_stage_use_dice = opt->use_dice_stage; m_role_order = opt->role_order; - m_force_air_defense.stop_blocking_deploy_num = opt->stop_deploy_blocking_num; - m_force_air_defense.deploy_air_defense_num = opt->force_deploy_air_defense_num; - m_force_air_defense.ban_medic = opt->force_ban_medic; m_force_deploy_direction = opt->force_deploy_direction; + m_force_air_defense = AirDefenseData { .stop_blocking_deploy_num = opt->stop_deploy_blocking_num, + .deploy_air_defense_num = opt->force_deploy_air_defense_num, + .ban_medic = opt->force_ban_medic }; } else { for (const auto& [loc, side] : m_normal_tile_info) { @@ -207,35 +191,17 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() m_homes.emplace_back(ReplacementHome { loc, battle::DeployDirection::None }); } } - m_stage_use_dice = true; + m_allow_to_use_dice = true; m_role_order = { battle::Role::Warrior, battle::Role::Pioneer, battle::Role::Medic, battle::Role::Tank, battle::Role::Sniper, battle::Role::Caster, battle::Role::Support, battle::Role::Special, battle::Role::Drone, }; } - m_wait_blocking.assign(m_homes.size(), true); - m_wait_medic.assign(m_homes.size(), true); - m_indeed_no_medic.assign(m_homes.size(), false); - // 认为开局时未在所有路线放干员时为紧急状态,并把路线压入栈 - // 由于开局路线设为0,故0不需要被压入栈 - m_cur_home_index = 0; - m_is_cur_urgent = true; - for (size_t index = m_homes.size() - 1; index > 0; index--) { - m_next_urgent_index.push(index); - } - if (opt && !opt->key_kills.empty()) { - std::string log_str = "[ "; - for (const auto& kills : opt->key_kills) { - m_key_kills.emplace(kills); - log_str += std::to_string(kills) + ", "; - } - log_str += "]"; - Log.info("key kills:", log_str); - } - + m_homes_status.resize(m_homes.size()); if (m_homes.empty()) { Log.error("Unknown home pos"); + return false; } auto cb_info = basic_info_with_what("StageInfo"); @@ -247,12 +213,7 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() return true; } -bool asst::RoguelikeBattleTaskPlugin::battle_pause() -{ - return ProcessTask(*this, { "BattlePause" }).run(); -} - -asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_role_location_type(const battle::Role& role) +asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_role_location_type(const battle::Role& role) const { switch (role) { case battle::Role::Medic: @@ -274,12 +235,12 @@ asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_role_location_ty } } -asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_oper_location_type(const std::string& name) +asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_oper_location_type(const std::string& name) const { return BattleData.get_location_type(name); } -asst::battle::OperPosition asst::RoguelikeBattleTaskPlugin::get_role_position(const battle::Role& role) +asst::battle::OperPosition asst::RoguelikeBattleTaskPlugin::get_role_position(const battle::Role& role) const { switch (role) { case battle::Role::Support: @@ -293,6 +254,8 @@ asst::battle::OperPosition asst::RoguelikeBattleTaskPlugin::get_role_position(co return battle::OperPosition::Blocking; break; case battle::Role::Medic: + return m_force_air_defense.ban_medic ? battle::OperPosition::None : battle::OperPosition::AirDefense; + break; case battle::Role::Special: case battle::Role::Drone: default: @@ -338,9 +301,8 @@ void asst::RoguelikeBattleTaskPlugin::set_position_full(const std::string& name, set_position_full(get_oper_location_type(name), full); } -bool asst::RoguelikeBattleTaskPlugin::get_position_full(const battle::Role& role) +bool asst::RoguelikeBattleTaskPlugin::get_position_full(const battle::LocationType& loc_type) const { - const auto& loc_type = get_role_location_type(role); switch (loc_type) { case battle::LocationType::Melee: return m_melee_full; @@ -359,10 +321,138 @@ bool asst::RoguelikeBattleTaskPlugin::get_position_full(const battle::Role& role return false; } +bool asst::RoguelikeBattleTaskPlugin::get_position_full(const battle::Role& role) const +{ + return get_position_full(get_role_location_type(role)); +} + +bool asst::RoguelikeBattleTaskPlugin::get_position_full(const std::string& name) const +{ + return get_position_full(get_oper_location_type(name)); +} + bool asst::RoguelikeBattleTaskPlugin::auto_battle() { - LogTraceFunction; + check_drone_tiles(); + if (!m_first_deploy && use_all_ready_skill()) { + return true; + } + + std::unordered_set pre_cooling; + for (const auto& [name, oper] : m_cur_deployment_opers) { + if (oper.cooling) pre_cooling.emplace(name); + } + auto pre_battlefield = m_battlefield_opers; + + update_deployment(); + + std::unordered_set cur_cooling; + std::vector cur_available; + for (const auto& [name, oper] : m_cur_deployment_opers) { + if (oper.cooling) cur_cooling.emplace(name); + if (oper.available) cur_available.emplace_back(name); + } + + bool deploy_dice_now = false; + auto urgent_home_opt = check_urgent(pre_cooling, cur_cooling, pre_battlefield, deploy_dice_now); + + size_t normal_home_index = m_cur_home_index; + if (!urgent_home_opt) { + // 超过一半的人费用都没好,且没有紧急情况,那就不下人 + // TODO: 这个逻辑非常不好,待优化 + size_t not_cooling_count = m_cur_deployment_opers.size() - cur_cooling.size(); + if (cur_available.size() <= not_cooling_count / 2) { + Log.trace("now_total", m_cur_deployment_opers.size(), ", available", cur_available.size(), ", not_cooling", + not_cooling_count, ", just wait a minute"); + return true; + } + } + else { + m_cur_home_index = *urgent_home_opt; + } + + Log.info("To path", m_cur_home_index); + + battle::DeploymentOper best_oper; + if (deploy_dice_now) { + best_oper = m_cur_deployment_opers[Dice]; + } + else if (auto best_oper_opt = calc_best_oper(cur_available)) { + best_oper = *best_oper_opt; + } + else { + return true; + } + + // 计算最优部署位置及方向 + auto best_loc_opt = calc_best_loc(best_oper); + if (!best_loc_opt) { + Log.info("Tiles full while calc best plan."); + return true; + } + const auto& [placed_loc, direction] = *best_loc_opt; + + deploy_oper(best_oper.name, placed_loc, direction); + postproc_of_deployments(best_oper, placed_loc, direction); + + if (urgent_home_opt) { + m_urgent_home_index.pop_front(); + m_cur_home_index = normal_home_index; + } + + ++m_cur_home_index; + if (m_cur_home_index >= m_homes.size()) { + m_cur_home_index = 0; + } + return true; +} + +void asst::RoguelikeBattleTaskPlugin::postproc_of_deployments(const battle::DeploymentOper& oper, + const Point& placed_loc, + battle::DeployDirection direction) +{ + // 如果是医疗干员,判断覆盖范围内有无第一次放置的干员 + if (oper.role == battle::Role::Medic) { + std::vector contain_index; + for (const Point& relative_pos : get_attack_range(oper, direction)) { + Point absolute_pos = placed_loc + relative_pos; + if (auto iter = m_blocking_for_home_index.find(absolute_pos); iter != m_blocking_for_home_index.end()) { + m_homes_status[iter->second].wait_medic = false; + contain_index.emplace_back(iter->second); + } + } + if (!contain_index.empty()) { + m_medic_for_home_index.emplace(placed_loc, std::move(contain_index)); + } + + m_homes_status[m_cur_home_index].wait_medic = false; + } + + auto position = get_role_position(oper.role); + switch (position) { + case OperPosition::Blocking: { + m_force_air_defense.has_deployed_blocking_num++; + bool& wait_blocking = m_homes_status[m_cur_home_index].wait_blocking; + if (wait_blocking) { + wait_blocking = false; + m_blocking_for_home_index.emplace(placed_loc, m_cur_home_index); + } + } break; + case OperPosition::AirDefense: + if (!m_force_air_defense.has_finished_deploy_air_defense) { + m_force_air_defense.has_deployed_air_defense_num++; + if (m_force_air_defense.has_deployed_air_defense_num >= m_force_air_defense.deploy_air_defense_num) { + m_force_air_defense.has_finished_deploy_air_defense = true; + Log.info("FORCE RANGED OPER DEPLOY END"); + } + } + break; + } +} + +void asst::RoguelikeBattleTaskPlugin::check_drone_tiles() +{ // 将存在于场上超过限时的召唤物所在的地块重新设为可用 Time_Point now_time = std::chrono::system_clock::now(); while ((!m_need_clear_tiles.empty()) && m_need_clear_tiles.top().placed_time < now_time) { @@ -370,491 +460,164 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() if (auto iter = m_used_tiles.find(placed_loc); iter != m_used_tiles.end()) { Log.info("Drone at location (", placed_loc.x, ",", placed_loc.y, ") is recognized as retreated"); set_position_full(placed_loc, false); - m_opers_in_field.erase(iter->second); + m_battlefield_opers.erase(iter->second.name); m_used_tiles.erase(iter); } m_need_clear_tiles.pop(); } +} - const cv::Mat& image = ctrler()->get_image(); - if (!m_first_deploy && try_possible_skill(image)) { - return true; +std::optional asst::RoguelikeBattleTaskPlugin::check_urgent( + const std::unordered_set& pre_cooling, const std::unordered_set& cur_cooling, + const std::map& pre_bf_opers, bool& deploy_dice_now) +{ + if (m_first_deploy) { + // No emergency + return std::nullopt; } - BattleImageAnalyzer battle_analyzer(image); - battle_analyzer.set_target(BattleImageAnalyzer::Target::Roguelike); - - if (!battle_analyzer.analyze()) { - return false; - } - - battle_analyzer.sort_opers_by_cost(); - auto opers = battle_analyzer.get_opers(); - if (opers.empty()) { - return true; - } - battle_analyzer.set_target(BattleImageAnalyzer::Target::Oper); - - if (m_cur_home_index >= m_homes.size()) { - m_cur_home_index = 0; - } - - int available_count = 0; - int cooling_count = 0; std::vector new_urgent; - std::vector cooling_opers; - const auto use_oper_task_ptr = Task.get("BattleUseOper"); - bool has_dice = false; - battle::DeploymentOper dice; - for (const auto& oper : opers) { - if (oper.cooling) cooling_count++; - if (oper.available) available_count++; - } - for (auto& oper : opers) { - if (oper.role != battle::Role::Drone) { + for (const auto& name : cur_cooling) { + if (pre_cooling.find(name) != pre_cooling.cend()) { continue; } - if (m_dice_image.empty()) { + Log.info(name, "retreated"); + auto pre_loc_iter = pre_bf_opers.find(name); + if (pre_loc_iter == pre_bf_opers.cend()) { + Log.error("the oper", name, "was not on the battlefield before"); continue; } - MatchImageAnalyzer dice_analyzer(oper.avatar); - dice_analyzer.set_task_info("DiceAvatarMatch"); - dice_analyzer.set_templ(m_dice_image); - if (!dice_analyzer.analyze()) { + Point pre_loc = pre_loc_iter->second.loc; + + if (auto del_loc_blocking = m_blocking_for_home_index.find(pre_loc); + del_loc_blocking != m_blocking_for_home_index.end()) { + Log.info("Urgent situation detected"); + + size_t home_index = del_loc_blocking->second; + m_homes_status[home_index].wait_blocking = true; + new_urgent.emplace_back(home_index); + m_blocking_for_home_index.erase(del_loc_blocking); + } + else if (auto del_loc_medic = m_medic_for_home_index.find(pre_loc); + del_loc_medic != m_medic_for_home_index.end()) { + for (const size_t& home_index : del_loc_medic->second) { + m_homes_status[home_index].wait_medic = true; + } + m_medic_for_home_index.erase(del_loc_medic); + } + set_position_full(pre_loc, false); + } + + // 同时出现了多个家门的紧急情况,index 序号小的是靠前的家门,优先入栈 + ranges::sort(new_urgent); + for (const size_t& home_index : new_urgent) { + if (ranges::find(m_urgent_home_index, home_index) != m_urgent_home_index.cend()) { continue; } - has_dice = true; - oper.name = Dice; - dice = oper; - break; + m_urgent_home_index.emplace_back(home_index); } - bool can_use_dice = m_stage_use_dice && has_dice; - if (!can_use_dice) { - m_use_dice = false; - } - // 如果发现有新撤退,就更新m_retreated_opers - // 如果发现有新转好的,只更新m_last_cooling_count,在部署时从set中删去 - if (cooling_count > m_last_cooling_count && !m_first_deploy) { - battle_pause(); - int remain_add = cooling_count - m_last_cooling_count; - for (size_t i = 0; i < opers.size(); ++i) { - const auto& cur_opers = battle_analyzer.get_opers(); - if (cur_opers.empty()) { - // 只会在战斗结束时发生,战斗结束后cur_opers为空,导致访问越界 - return true; - } - size_t offset = opers.size() > cur_opers.size() ? opers.size() - cur_opers.size() : 0; - const auto& oper = cur_opers.at(i - offset); - if ((!oper.cooling) || oper.role == battle::Role::Drone) { - continue; - } - ctrler()->click(oper.rect); - sleep(use_oper_task_ptr->pre_delay); - const cv::Mat& new_image = ctrler()->get_image(); - - OcrWithPreprocessImageAnalyzer oper_name_analyzer(new_image); - oper_name_analyzer.set_task_info("BattleOperName"); - oper_name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map); - - std::string oper_name = UnknownName; - if (oper_name_analyzer.analyze()) { - oper_name_analyzer.sort_result_by_score(); - oper_name = oper_name_analyzer.get_result().front().text; - } - - battle_analyzer.set_image(new_image); - battle_analyzer.analyze(); - battle_analyzer.sort_opers_by_cost(); - - if (is_oper_name_error(oper_name)) { - continue; - } - - if (m_retreated_opers.contains(oper_name)) { - continue; - } - auto iter = m_opers_in_field.find(oper_name); - if (iter == m_opers_in_field.end()) { - continue; - } - - Log.info(oper_name, "retreated"); - if (auto del_pos_blocking = m_blocking_for_home_index.find(iter->second); - del_pos_blocking != m_blocking_for_home_index.end()) { - m_wait_blocking[del_pos_blocking->second] = true; - new_urgent.emplace_back(del_pos_blocking->second); - m_blocking_for_home_index.erase(del_pos_blocking); - } - else if (auto del_pos_medic = m_medic_for_home_index.find(iter->second); - del_pos_medic != m_medic_for_home_index.end()) { - for (const size_t& home_index : del_pos_medic->second) { - m_wait_medic[home_index] = true; - } - m_medic_for_home_index.erase(del_pos_medic); - } - if (auto del_pos_tiles = m_used_tiles.find(iter->second); del_pos_tiles != m_used_tiles.end()) { - if (m_normal_tile_info[del_pos_tiles->first].buildable == battle::LocationType::Melee) { - m_force_air_defense.has_deployed_blocking_num--; - } - m_used_tiles.erase(del_pos_tiles); - } - set_position_full(iter->second, false); - m_opers_in_field.erase(iter); - m_retreated_opers.emplace(oper_name); - remain_add--; - if (!remain_add) break; - } - battle_pause(); - cancel_oper_selection(); - } - m_last_cooling_count = cooling_count; - if (!new_urgent.empty()) { - // 出现新的紧急情况,立即切到这条线路,并把其他紧急情况压入栈 - Log.info("New urgent situation detected"); - if (m_is_cur_urgent) { - m_next_urgent_index.push(m_cur_home_index); - } - else { - m_last_not_urgent = static_cast(m_cur_home_index); - } - m_cur_home_index = new_urgent.at(0); - for (size_t i = 1; i < new_urgent.size(); ++i) { - m_next_urgent_index.push(new_urgent.at(i)); - } - if (can_use_dice) { - // 先放骰子,再放近战 - m_use_dice = true; - m_next_urgent_index.push(new_urgent.at(0)); - } - } - else if ((!m_is_cur_urgent) && m_used_tiles.size() >= 2) { - // 超过一半的人费用都没好,且没有紧急情况,那就不下人 - size_t not_cooling_count = opers.size() - cooling_count; - if (available_count <= not_cooling_count / 2) { - Log.trace("already used", m_used_tiles.size(), ", now_total", opers.size(), ", available", available_count, - ", not_cooling", not_cooling_count); - return true; - } + if (m_urgent_home_index.empty()) { + deploy_dice_now = false; + return std::nullopt; } - const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper"); + deploy_dice_now = m_allow_to_use_dice && m_cur_deployment_opers.find(Dice) != m_cur_deployment_opers.cend(); + return m_urgent_home_index.front(); +} - // 点击当前最合适的干员 - battle::DeploymentOper opt_oper; - bool oper_found = false; - - bool has_blocking = false; +std::optional asst::RoguelikeBattleTaskPlugin::calc_best_oper( + std::vector& cur_available) +{ bool has_medic = false; - bool wait_blocking = m_wait_blocking[m_cur_home_index]; - bool wait_medic = m_wait_medic[m_cur_home_index]; - bool force_need_air_defense = - (!m_force_air_defense.has_finished_deploy_air_defense) && - (m_force_air_defense.has_deployed_blocking_num >= m_force_air_defense.stop_blocking_deploy_num) && - (!m_use_dice); - if (m_use_dice) { - opt_oper = std::move(dice); - oper_found = true; - if (available_locations(battle::LocationType::Melee).empty()) { - m_melee_full = true; - Log.info("Tiles full"); - return true; + bool has_blocking = false; + bool has_air_defense = false; + for (const std::string& name : cur_available) { + const auto& role = m_cur_deployment_opers[name].role; + switch (role) { + case Role::Medic: + has_medic = true; + break; + } + + const auto& position = get_role_position(role); + switch (position) { + case OperPosition::Blocking: + has_blocking = true; + break; + case OperPosition::AirDefense: + has_air_defense = true; + break; + } + + // found all + if (has_medic && has_blocking && has_air_defense) { + break; } } - else { - // 对于每个蓝门,先下个地面单位(如果有的话) - // 第二个人下奶(如果有的话) - bool has_air_defense = false; - for (const auto& op : opers) { - if (op.cooling) { - continue; - } - if (op.role == battle::Role::Medic) { - has_medic = true; - } - battle::OperPosition position = get_role_position(op.role); - if (position == battle::OperPosition::Blocking) { - has_blocking = true; - } - else if (position == battle::OperPosition::AirDefense) { - if (m_force_air_defense.ban_medic && op.role == battle::Role::Medic) { - continue; - } - has_air_defense = true; - } + + bool force_air_defense = + !m_force_air_defense.has_finished_deploy_air_defense && + m_force_air_defense.has_deployed_blocking_num >= m_force_air_defense.stop_blocking_deploy_num; + + if (force_air_defense) { + Log.info("RANGED ROLE IS NEEDED UNDER FORCE"); + if (!has_air_defense) { + m_force_air_defense.has_finished_deploy_air_defense = true; + Log.info("FORCE RANGED OPER DEPLOY END"); + return std::nullopt; + } + } + + bool use_blocking = has_blocking && m_homes_status[m_cur_home_index].wait_blocking; + bool use_medic = has_medic && m_homes_status[m_cur_home_index].wait_medic; + + // 费用高的优先用,放前面 + ranges::sort(cur_available, [&](const std::string& lhs, const std::string& rhs) { + return m_cur_deployment_opers[lhs].cost > m_cur_deployment_opers[rhs].cost; + }); + + DeploymentOper best_oper; + + for (auto role : m_role_order) { + battle::OperPosition position = get_role_position(role); + if (force_air_defense && position != battle::OperPosition::AirDefense) { + continue; + } + else if (use_blocking && position != battle::OperPosition::Blocking) { + continue; + } + else if (use_medic && role != battle::Role::Medic) { + continue; } - wait_blocking &= has_blocking; - wait_medic &= has_medic; - if (force_need_air_defense) { - Log.info("RANGED ROLE IS NEEDED UNDER FORCE"); - if (!has_air_defense) { - m_force_air_defense.has_finished_deploy_air_defense = true; - Log.info("FORCE RANGED OPER DEPLOY END"); - return true; - } + if (get_position_full(role)) { + continue; } - for (auto role : m_role_order) { - battle::OperPosition position = get_role_position(role); - if (force_need_air_defense) { - if (position != battle::OperPosition::AirDefense) continue; - if (m_force_air_defense.ban_medic && role == battle::Role::Medic) continue; - } - else { - if (wait_blocking) { - if (position != battle::OperPosition::Blocking) continue; - } - else if (wait_medic) { - if (role != battle::Role::Medic) { - continue; - } - } - } - if (get_position_full(role)) { + for (const std::string& name : cur_available) { + if (name == Dice) { continue; } - - for (const auto& oper : opers) { - if (!oper.available) { - continue; - } - if (oper.name == Dice) { - continue; - } - if (oper.role == role) { - opt_oper = oper; - oper_found = true; - break; - } + if (get_position_full(name)) { + continue; } - if (oper_found) { + const auto& oper = m_cur_deployment_opers[name]; + + if (oper.role == role) { + best_oper = oper; break; } } - if (!oper_found) { - return true; - } - - // 预计算干员是否有点地方放 - if (available_locations(opt_oper.role).empty()) { - set_position_full(opt_oper.role, true); - Log.info("Tiles full"); - if (force_need_air_defense) { - m_force_air_defense.has_finished_deploy_air_defense = true; - Log.info("FORCE RANGED OPER DEPLOY END"); - } - return true; - } - - if (m_first_deploy) { - m_first_deploy = false; - battle_pause(); - bool clicked_drone = false; - for (size_t i = 0; i < opers.size(); ++i) { - const auto& cur_opers = battle_analyzer.get_opers(); - if (cur_opers.empty()) { - // 只会在战斗结束时发生,战斗结束后cur_opers为空,导致访问越界 - return true; - } - size_t offset = opers.size() > cur_opers.size() ? opers.size() - cur_opers.size() : 0; - const auto& oper = cur_opers.at(i - offset); - if (oper.role == battle::Role::Drone) { - clicked_drone = true; - ctrler()->click(oper.rect); - sleep(use_oper_task_ptr->pre_delay); - const cv::Mat& new_image = ctrler()->get_image(); - - OcrWithPreprocessImageAnalyzer oper_name_analyzer(new_image); - oper_name_analyzer.set_task_info("BattleOperName"); - oper_name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map); - - battle_analyzer.set_image(new_image); - battle_analyzer.analyze(); - battle_analyzer.sort_opers_by_cost(); - - if (!oper_name_analyzer.analyze()) continue; - oper_name_analyzer.sort_result_by_score(); - if (oper_name_analyzer.get_result().front().text.find(Dice) != std::string::npos) { - m_dice_image = opers.at(i).avatar; - Log.info("Dice detected"); - break; - } - } - } - battle_pause(); - if (clicked_drone) { - cancel_oper_selection(); - } - } - - ctrler()->click(opt_oper.rect); - sleep(use_oper_task_ptr->pre_delay); - - OcrWithPreprocessImageAnalyzer oper_name_analyzer(ctrler()->get_image()); - oper_name_analyzer.set_task_info("BattleOperName"); - oper_name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map); - - opt_oper.name = UnknownName; - if (oper_name_analyzer.analyze()) { - oper_name_analyzer.sort_result_by_score(); - opt_oper.name = oper_name_analyzer.get_result().front().text; - } - if (opt_oper.name == "阿米娅" && opt_oper.role == battle::Role::Warrior) { - opt_oper.name = "阿米娅-WARRIOR"; - } - - if (!is_oper_name_error(opt_oper.name)) { - auto real_loc_type = get_oper_location_type(opt_oper.name); - if (real_loc_type != battle::LocationType::Invalid && // 说明名字识别错了 - real_loc_type != battle::LocationType::All && real_loc_type != get_role_location_type(opt_oper.role)) { - // 重新计算干员是否有地方放 - if (available_locations(opt_oper.name).empty()) { - set_position_full(opt_oper.name, true); - Log.info("re-calc available loc, Tiles full"); - // TODO: 这里可能存在一个问题: - // 如果有地面位置,但没高台位置了,尝试“高台先锋”这种职业时 - // 前面的逻辑会因为他是“先锋”点开他,但是识别到名字确定是高台单位后,又会取消 - // 会一直循环上面的这个操作 - cancel_oper_selection(); - return true; - } - } - } - - if (opt_oper.name == Dice) { - // 在drone被认为是近战时必须加上 - cancel_oper_selection(); - return true; + if (!best_oper.name.empty()) { + break; } } - - // 计算最优部署位置及方向 - const auto& [placed_loc, direction] = calc_best_plan(opt_oper); - if (placed_loc == Point::zero() && direction == Point::zero()) { - Log.info("Tiles full while calc best plan."); - cancel_oper_selection(); - return true; + if (best_oper.name.empty()) { + return std::nullopt; } - // 将干员拖动到场上 - Point placed_point = m_side_tile_info.at(placed_loc).pos; - Rect placed_rect(placed_point.x, placed_point.y, 1, 1); - int dist = static_cast(Point::distance( - placed_point, { opt_oper.rect.x + opt_oper.rect.width / 2, opt_oper.rect.y + opt_oper.rect.height / 2 })); - // 1000 是随便取的一个系数,把整数的 pre_delay 转成小数用的 - int duration = static_cast(dist / 1000.0 * swipe_oper_task_ptr->pre_delay); - bool deploy_with_pause = ctrler()->support_swipe_with_pause(); - ctrler()->swipe(opt_oper.rect, placed_rect, duration, false, swipe_oper_task_ptr->special_params.at(1), - swipe_oper_task_ptr->special_params.at(2), deploy_with_pause); - sleep(use_oper_task_ptr->post_delay); - - // 将方向转换为实际的 swipe end 坐标点 - if (direction != Point::zero()) { - static const int coeff = swipe_oper_task_ptr->special_params.at(0); - Point end_point = placed_point + (direction * coeff); - ctrler()->swipe(placed_point, end_point, swipe_oper_task_ptr->post_delay); - sleep(use_oper_task_ptr->post_delay); - } - - if (opt_oper.role == battle::Role::Drone) { - cancel_oper_selection(); - now_time = std::chrono::system_clock::now(); - if (opt_oper.name == Dice) { - m_need_clear_tiles.emplace(now_time + std::chrono::seconds(20), placed_loc); - } - else { - m_need_clear_tiles.emplace(now_time + std::chrono::seconds(35), placed_loc); - } - } - if (deploy_with_pause) { - ctrler()->press_esc(); - } - - Log.info("Try to deploy oper", opt_oper.name); - m_opers_used = true; - m_used_tiles.emplace(placed_loc, opt_oper.name); - m_opers_in_field.emplace(opt_oper.name, placed_loc); - m_retreated_opers.erase(opt_oper.name); - if (get_role_position(opt_oper.role) == battle::OperPosition::Blocking) { - m_force_air_defense.has_deployed_blocking_num++; - } - if (force_need_air_defense) { - m_force_air_defense.has_deployed_air_defense_num++; - if (m_force_air_defense.has_deployed_air_defense_num >= m_force_air_defense.deploy_air_defense_num) { - m_force_air_defense.has_finished_deploy_air_defense = true; - Log.info("FORCE RANGED OPER DEPLOY END"); - } - // 不改变当前index,直接进入下一步 - return true; - } - - if (wait_blocking) { - m_wait_blocking[m_cur_home_index] = false; - m_blocking_for_home_index.emplace(placed_loc, m_cur_home_index); - } - else if (wait_medic) { - // 两次轮到wait_medic的轮次时仍没有医疗干员奶当前位置,即认为奶不到 - if (m_wait_medic[m_cur_home_index]) { - m_indeed_no_medic[m_cur_home_index] = false; - } - else { - if (m_indeed_no_medic[m_cur_home_index]) { - m_wait_medic[m_cur_home_index] = false; - } - else { - m_indeed_no_medic[m_cur_home_index] = true; - } - } - } - - // 如果有紧急需要部署的路线,那么下一条路线设为它 - if (m_next_urgent_index.empty()) { - if (m_last_not_urgent != -1) { - // 从上一次不是紧急的下一条路线开始 - m_cur_home_index = static_cast(m_last_not_urgent) + 1; - m_last_not_urgent = -1; - } - else { - ++m_cur_home_index; - } - m_is_cur_urgent = false; - } - else { - if (m_last_not_urgent == -1) { - m_last_not_urgent = static_cast(m_cur_home_index); - } - m_is_cur_urgent = true; - m_cur_home_index = m_next_urgent_index.top(); - Log.info("Enter urgent situation"); - m_next_urgent_index.pop(); - } - Log.info("To path", m_cur_home_index); - - return true; -} - -bool asst::RoguelikeBattleTaskPlugin::speed_up() -{ - return ProcessTask(*this, { "RoguelikeBattleSpeedUp" }).run(); -} - -bool asst::RoguelikeBattleTaskPlugin::use_skill(const Rect& rect) -{ - ctrler()->click(rect); - sleep(Task.get("BattleUseOper")->pre_delay); - - ProcessTask task(*this, { "BattleUseSkillJustClick" }); - task.set_retry_times(0); - return task.run(); -} - -bool asst::RoguelikeBattleTaskPlugin::retreat(const Point& point) -{ - ctrler()->click(point); - sleep(Task.get("BattleUseOper")->pre_delay); - - return ProcessTask(*this, { "BattleOperRetreatJustClick" }).run(); + return best_oper; } bool asst::RoguelikeBattleTaskPlugin::abandon() @@ -868,190 +631,40 @@ void asst::RoguelikeBattleTaskPlugin::all_melee_retreat() auto& tile_info = m_normal_tile_info[loc]; auto& type = tile_info.buildable; if (type == battle::LocationType::Melee || type == battle::LocationType::All) { - retreat(tile_info.pos); + retreat_oper(loc); } } } void asst::RoguelikeBattleTaskPlugin::clear() { - m_opers_used = false; - m_pre_hp = 0; m_homes.clear(); - m_wait_blocking.clear(); - m_wait_medic.clear(); - m_indeed_no_medic.clear(); + m_homes_status.clear(); m_blocking_for_home_index.clear(); m_medic_for_home_index.clear(); - decltype(m_next_urgent_index) empty_stack; - m_next_urgent_index.swap(empty_stack); - m_is_cur_urgent = false; - m_last_not_urgent = -1; + m_urgent_home_index = decltype(m_urgent_home_index)(); m_blacklist_location.clear(); - m_retreated_opers.clear(); - decltype(m_need_clear_tiles) empty_heap; - m_need_clear_tiles.swap(empty_heap); - decltype(m_key_kills) empty_queue; - m_key_kills.swap(empty_queue); + m_need_clear_tiles = decltype(m_need_clear_tiles)(); m_cur_home_index = 0; - m_stage_name.clear(); - m_side_tile_info.clear(); - m_used_tiles.clear(); - m_opers_in_field.clear(); - m_kills = 0; - m_total_kills = 0; - m_use_dice = false; - m_stage_use_dice = true; - m_dice_image = cv::Mat(); + m_allow_to_use_dice = true; m_first_deploy = true; - m_force_air_defense.clear(); - m_last_cooling_count = 0; + m_force_air_defense = decltype(m_force_air_defense)(); m_force_deploy_direction.clear(); m_melee_full = false; m_ranged_full = false; - - for (auto& [key, status_value] : m_restore_status) { - status()->set_number(key, status_value); - } - m_restore_status.clear(); } -bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image) -{ - if (!check_key_kills(image)) { - return false; - } - - BattleSkillReadyImageAnalyzer skill_analyzer(image); - bool used = false; - for (auto& [loc, oper_name] : m_used_tiles) { - std::string status_key = Status::RoguelikeSkillUsagePrefix + oper_name; - auto usage = battle::SkillUsage::Possibly; - auto usage_opt = status()->get_number(status_key); - if (usage_opt) { - usage = static_cast(usage_opt.value()); - } - - if (usage != battle::SkillUsage::Possibly && usage != battle::SkillUsage::Once) { - continue; - } - const Point pos = m_normal_tile_info.at(loc).pos; - skill_analyzer.set_base_point(pos); - if (!skill_analyzer.analyze()) { - continue; - } - ctrler()->click(pos); - sleep(Task.get("BattleUseOper")->pre_delay); - bool ret = ProcessTask(*this, { "BattleSkillReadyOnClick" }).set_retry_times(2).run(); - if (!ret) { - cancel_oper_selection(); - } - used |= ret; - if (usage == battle::SkillUsage::Once) { - status()->set_number(status_key, static_cast(battle::SkillUsage::OnceUsed)); - m_restore_status[status_key] = static_cast(battle::SkillUsage::Once); - } - } - return used; -} - -bool asst::RoguelikeBattleTaskPlugin::check_key_kills(const cv::Mat& image) -{ - if (m_key_kills.empty()) { - return true; - } - int need_kills = m_key_kills.front(); - - BattleImageAnalyzer analyzer(image); - if (m_total_kills) { - analyzer.set_pre_total_kills(m_total_kills); - } - analyzer.set_target(BattleImageAnalyzer::Target::Kills); - if (analyzer.analyze()) { - m_kills = analyzer.get_kills(); - m_total_kills = analyzer.get_total_kills(); - if (m_kills >= need_kills) { - m_key_kills.pop(); - return true; - } - } - return false; -} - -bool asst::RoguelikeBattleTaskPlugin::wait_start() -{ - auto start_time = std::chrono::system_clock::now(); - auto check_time = [&]() -> bool { - using namespace std::chrono_literals; - return std::chrono::system_clock::now() - start_time > 1min; - }; - - MatchImageAnalyzer officially_begin_analyzer; - officially_begin_analyzer.set_task_info("BattleOfficiallyBegin"); - cv::Mat image; - while (!need_exit() && !check_time()) { - image = ctrler()->get_image(); - officially_begin_analyzer.set_image(image); - if (officially_begin_analyzer.analyze()) { - break; - } - std::this_thread::yield(); - } - - BattleImageAnalyzer oper_analyzer; - oper_analyzer.set_target(BattleImageAnalyzer::Target::Oper); - while (!need_exit() && !check_time()) { - image = ctrler()->get_image(); - oper_analyzer.set_image(image); - if (oper_analyzer.analyze()) { - break; - } - std::this_thread::yield(); - } - - // 识别一帧总击杀数 - BattleImageAnalyzer kills_analyzer(image); - kills_analyzer.set_target(BattleImageAnalyzer::Target::Kills); - if (kills_analyzer.analyze()) { - m_kills = kills_analyzer.get_kills(); - m_total_kills = kills_analyzer.get_total_kills(); - } - - if (!m_stage_name.empty()) { - for (const auto& [loc, info] : m_normal_tile_info) { - std::string text = "( " + std::to_string(loc.x) + ", " + std::to_string(loc.y) + " )"; - cv::putText(image, text, cv::Point(info.pos.x - 30, info.pos.y), 1, 1.2, cv::Scalar(0, 0, 255), 2); - } - asst::imwrite("map/" + m_stage_name + ".png", image); - } - else { - // 存出来的是带时间戳的文件名 - kills_analyzer.save_img("map/"); - } - return true; -} - -bool asst::RoguelikeBattleTaskPlugin::cancel_oper_selection() -{ - return ProcessTask(*this, { "BattleCancelSelection" }).run(); -} - -bool asst::RoguelikeBattleTaskPlugin::is_oper_name_error(const std::string& name) -{ - return name == UnknownName || get_oper_location_type(name) == battle::LocationType::Invalid; -} - -std::vector asst::RoguelikeBattleTaskPlugin::available_locations(battle::Role role) +std::vector asst::RoguelikeBattleTaskPlugin::available_locations(battle::Role role) const { return available_locations(get_role_location_type(role)); } -std::vector asst::RoguelikeBattleTaskPlugin::available_locations(const std::string& name) +std::vector asst::RoguelikeBattleTaskPlugin::available_locations(const std::string& name) const { return available_locations(get_oper_location_type(name)); } -std::vector asst::RoguelikeBattleTaskPlugin::available_locations(battle::LocationType type) +std::vector asst::RoguelikeBattleTaskPlugin::available_locations(battle::LocationType type) const { std::vector result; for (const auto& [loc, tile] : m_normal_tile_info) { @@ -1067,7 +680,8 @@ std::vector asst::RoguelikeBattleTaskPlugin::available_locations(ba return result; } -asst::battle::AttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(const battle::DeploymentOper& oper) +asst::battle::AttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(const battle::DeploymentOper& oper, + DeployDirection direction) const { int64_t elite = status()->get_number(Status::RoguelikeCharElitePrefix + oper.name).value_or(0); battle::AttackRange right_attack_range = BattleData.get_range(oper.name, elite); @@ -1110,151 +724,146 @@ asst::battle::AttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(cons } } + for (auto cur_direction : + { DeployDirection::Right, DeployDirection::Up, DeployDirection::Left, DeployDirection::Down }) { + if (cur_direction == direction) break; + + // rotate relative attack range counterclockwise + for (Point& point : right_attack_range) + point = { point.y, -point.x }; + } + return right_attack_range; } -asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::calc_best_plan( - const battle::DeploymentOper& oper) +std::optional asst::RoguelikeBattleTaskPlugin::calc_best_loc( + const battle::DeploymentOper& oper) const { - if (m_cur_home_index >= m_homes.size()) { - m_cur_home_index = 0; - } - - Point home(5, 5); // 实在找不到家门了,随便取个点当家门用算了,一般是地图的中间 - Point recommended_direction; - static const std::unordered_map direction_map = { - { battle::DeployDirection::Up, Point::up() }, { battle::DeployDirection::Down, Point::down() }, - { battle::DeployDirection::Left, Point::left() }, { battle::DeployDirection::Right, Point::right() }, - { battle::DeployDirection::None, Point() }, - }; - if (m_cur_home_index < m_homes.size()) { - const auto& rp_home = m_homes.at(m_cur_home_index); - home = rp_home.location; - recommended_direction = direction_map.at(rp_home.direction); + size_t home_index = m_cur_home_index; + if (home_index >= m_homes.size()) { + Log.warn("home index is out of range", m_cur_home_index, m_homes.size()); + home_index = 0; } + const ReplacementHome& home = m_homes[home_index]; auto comp_dist = [&](const Point& lhs, const Point& rhs) -> bool { - int lhs_y_dist = std::abs(lhs.y - home.y); - int lhs_dist = std::abs(lhs.x - home.x) + lhs_y_dist; - int rhs_y_dist = std::abs(rhs.y - home.y); - int rhs_dist = std::abs(rhs.x - home.x) + rhs_y_dist; + int lhs_y_dist = std::abs(lhs.y - home.location.y); + int lhs_dist = std::abs(lhs.x - home.location.x) + lhs_y_dist; + int rhs_y_dist = std::abs(rhs.y - home.location.y); + int rhs_dist = std::abs(rhs.x - home.location.x) + rhs_y_dist; // 距离一样选择 x 轴上的,因为一般的地图都是横向的长方向 return lhs_dist == rhs_dist ? lhs_y_dist < rhs_y_dist : lhs_dist < rhs_dist; }; - - std::vector available_loc = - is_oper_name_error(oper.name) ? available_locations(oper.role) : available_locations(oper.name); - + std::vector available_loc = available_locations(oper.name); if (available_loc.empty()) { Log.error("No available locations"); - return {}; + return std::nullopt; } - // 把所有可用的点按距离排个序 ranges::sort(available_loc, comp_dist); if (oper.name == Dice) { - return { available_loc.back(), Point::zero() }; + return DeployInfo { available_loc.back(), DeployDirection::None }; } - Point best_location, best_direction; + Point best_location; + DeployDirection best_direction = DeployDirection::None; int max_score = INT_MIN; const auto& near_loc = available_loc.front(); - int min_dist = std::abs(near_loc.x - home.x) + std::abs(near_loc.y - home.y); + int min_dist = std::abs(near_loc.x - home.location.x) + std::abs(near_loc.y - home.location.y); // 取距离最近的N个点,计算分数。然后使用得分最高的点 constexpr int CalcPointCount = 4; for (const auto& loc : available_loc | views::take(CalcPointCount)) { - auto cur_result = calc_best_direction_and_score(loc, oper, recommended_direction); + const auto& [cur_direction, cur_socre] = calc_best_direction_and_score(loc, oper, home.direction); // 离得远的要扣分 constexpr int DistWeights = -1050; - int extra_dist = std::abs(loc.x - home.x) + std::abs(loc.y - home.y) - min_dist; + int extra_dist = std::abs(loc.x - home.location.x) + std::abs(loc.y - home.location.y) - min_dist; int extra_dist_score = DistWeights * extra_dist; if (oper.role == battle::Role::Medic) { // 医疗干员离得远无所谓 extra_dist_score = 0; } - if (cur_result.second + extra_dist_score > max_score) { - max_score = cur_result.second + extra_dist_score; + if (cur_socre + extra_dist_score > max_score) { + max_score = cur_socre + extra_dist_score; best_location = loc; - best_direction = cur_result.first; + best_direction = cur_direction; } } // 强制变化为确定的攻击方向 if (auto iter = m_force_deploy_direction.find(best_location); iter != m_force_deploy_direction.end()) { if (iter->second.role.contains(oper.role)) { - best_direction = direction_map.at(iter->second.direction); + best_direction = iter->second.direction; } } - // 如果是医疗干员,判断覆盖范围内有无第一次放置的干员 - if (oper.role == battle::Role::Medic) { - battle::AttackRange right_attack_range = get_attack_range(oper); - for (const Point& direction : { Point::right(), Point::up(), Point::left(), Point::down() }) { - if (direction == best_direction) break; - for (Point& point : right_attack_range) - point = { point.y, -point.x }; - } - std::vector contain_index; - for (const Point& relative_pos : right_attack_range) { - Point absolute_pos = best_location + relative_pos; - if (auto iter = m_blocking_for_home_index.find(absolute_pos); iter != m_blocking_for_home_index.end()) { - m_wait_medic[iter->second] = false; - contain_index.emplace_back(iter->second); - } - } - if (!contain_index.empty()) { - m_medic_for_home_index.emplace(best_location, std::move(contain_index)); - } - } - - return { best_location, best_direction }; + return DeployInfo { best_location, best_direction }; } -std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score( - Point loc, const battle::DeploymentOper& oper, Point recommended_direction) +asst::RoguelikeBattleTaskPlugin::DirectionAndScore asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score( + Point loc, const battle::DeploymentOper& oper, DeployDirection recommended_direction) const { LogTraceFunction; - // 根据家门的方向计算一下大概的朝向 - if (m_cur_home_index >= m_homes.size()) { - m_cur_home_index = 0; - } - Point home_loc(5, 5); - if (m_cur_home_index < m_homes.size()) { - home_loc = m_homes.at(m_cur_home_index).location; + size_t home_index = m_cur_home_index; + if (home_index >= m_homes.size()) { + Log.warn("home index is out of range", m_cur_home_index, m_homes.size()); + home_index = 0; } + Point home_loc = m_homes[home_index].location; - auto sgn = [](const int& x) -> int { - if (x > 0) return 1; - if (x < 0) return -1; - return 0; - }; - - Point base_direction(0, 0); + DeployDirection base_direction = DeployDirection::None; if (loc.x == home_loc.x) { - base_direction.y = sgn(loc.y - home_loc.y); + if (loc.y - home_loc.y >= 0) { + base_direction = DeployDirection::Up; + } + else { + base_direction = DeployDirection::Down; + } } else { - base_direction.x = sgn(loc.x - home_loc.x); - } - Point home_direction(-base_direction.x, -base_direction.y); - // 医疗反着算 - if (oper.role == battle::Role::Medic) { - base_direction = -base_direction; + if (loc.x - home_loc.x >= 0) { + base_direction = DeployDirection::Right; + } + else { + base_direction = DeployDirection::Left; + } } - // 按朝右算,后面根据方向做转换 - battle::AttackRange right_attack_range = get_attack_range(oper); + // 家门反过来 + DeployDirection home_direction = DeployDirection::None; + switch (base_direction) { + case DeployDirection::Right: + home_direction = DeployDirection::Left; + break; + case DeployDirection::Up: + home_direction = DeployDirection::Down; + break; + case DeployDirection::Left: + home_direction = DeployDirection::Right; + break; + case DeployDirection::Down: + home_direction = DeployDirection::Up; + break; + default: + break; + } + + // 医疗优先朝向家门 + if (oper.role == battle::Role::Medic) { + base_direction = home_direction; + } int max_score = 0; - Point opt_direction; + DeployDirection best_direction = DeployDirection::None; - for (const Point& direction : { Point::right(), Point::up(), Point::left(), Point::down() }) { + for (auto direction : + { DeployDirection::Right, DeployDirection::Up, DeployDirection::Left, DeployDirection::Down }) { int score = 0; - for (const Point& relative_pos : right_attack_range) { + // 按朝右算,后面根据方向做转换 + for (const Point& relative_pos : get_attack_range(oper, direction)) { Point absolute_pos = loc + relative_pos; using TileKey = TilePack::TileKey; @@ -1281,7 +890,7 @@ std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction case battle::Role::Medic: if (auto iter = m_used_tiles.find(absolute_pos); iter != m_used_tiles.cend() && - BattleData.get_role(iter->second) != battle::Role::Drone) // 根据哪个方向上人多决定朝向哪 + BattleData.get_role(iter->second.name) != battle::Role::Drone) // 根据哪个方向上人多决定朝向哪 score += 10000; if (auto iter = m_side_tile_info.find(absolute_pos); iter != m_side_tile_info.end()) score += TileKeyMedicWeights.at(iter->second.key); @@ -1296,24 +905,17 @@ std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction if (direction == base_direction) { score += 300; } - if (oper.role != battle::Role::Medic && direction == home_direction) { score -= 500; } - if (oper.role != battle::Role::Medic && direction == recommended_direction) { score += 2000; } - if (score > max_score) { max_score = score; - opt_direction = direction; + best_direction = direction; } - - // rotate relative attack range counterclockwise - for (Point& point : right_attack_range) - point = { point.y, -point.x }; } - return std::make_pair(opt_direction, max_score); + return { best_direction, max_score }; } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h index 13c5ff5a08..b6a0de8f93 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h +++ b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h @@ -12,7 +12,7 @@ namespace asst { - class RoguelikeBattleTaskPlugin : public AbstractTaskPlugin, public BattleHelper + class RoguelikeBattleTaskPlugin : public AbstractTaskPlugin, private BattleHelper { using Time_Point = std::chrono::time_point; @@ -25,57 +25,105 @@ namespace asst virtual bool verify(AsstMsg msg, const json::value& details) const override; - void set_stage_name(std::string stage); - protected: virtual bool _run() override; // 有些特殊的角色,他的职业并不一定和正常的位置相对应,比如“掠风”是地面辅助 - // get_role_position 可以仅知道干员职业的情况下,大概猜测一下位置 - // get_oper_position 可以在已知干员名的时候获得准确的位置 - battle::LocationType get_role_location_type(const battle::Role& role); - battle::LocationType get_oper_location_type(const std::string& name); + // get_role_location_type 可以仅知道干员职业的情况下,大概猜测一下位置 + // get_oper_location_type 可以在已知干员名的时候获得准确的位置 + battle::LocationType get_role_location_type(const battle::Role& role) const; + battle::LocationType get_oper_location_type(const std::string& name) const; - std::vector available_locations(battle::Role role); - std::vector available_locations(const std::string& name); - std::vector available_locations(battle::LocationType type); + std::vector available_locations(battle::Role role) const; + std::vector available_locations(const std::string& name) const; + std::vector available_locations(battle::LocationType type) const; - battle::OperPosition get_role_position(const battle::Role& role); + battle::OperPosition get_role_position(const battle::Role& role) const; - void wait_for_start(); - bool get_stage_info(); - bool battle_pause(); bool auto_battle(); - void all_melee_retreat(); - bool speed_up(); - bool use_skill(const Rect& rect); - bool retreat(const Point& point); - bool abandon(); + bool calc_stage_info(); + void clear(); - bool try_possible_skill(const cv::Mat& image); - bool check_key_kills(const cv::Mat& image); - bool wait_start(); - bool cancel_oper_selection(); - bool is_oper_name_error(const std::string& name); + void all_melee_retreat(); + bool abandon(); void set_position_full(const battle::LocationType& loc_type, bool full); void set_position_full(const Point& point, bool full); void set_position_full(const battle::Role& role, bool full); void set_position_full(const std::string& name, bool full); - bool get_position_full(const battle::Role& role); + + bool get_position_full(const battle::LocationType& loc_type) const; + bool get_position_full(const battle::Role& role) const; + bool get_position_full(const std::string& name) const; + + std::optional check_urgent(const std::unordered_set& pre_cooling, + const std::unordered_set& cur_cooling, + const std::map& pre_bf_opers, + bool& deploy_dice_now); + std::optional calc_best_oper(std::vector& cur_available); struct DeployInfo { Point placed; - Point direction; + battle::DeployDirection direction; }; - battle::AttackRange get_attack_range(const battle::DeploymentOper& oper); - DeployInfo calc_best_plan(const battle::DeploymentOper& oper); + std::optional calc_best_loc(const battle::DeploymentOper& oper) const; + battle::AttackRange get_attack_range(const battle::DeploymentOper& oper, + battle::DeployDirection direction = battle::DeployDirection::Right) const; - // 计算摆放干员的朝向 - // 返回滑动的方向、得分 - std::pair calc_best_direction_and_score(Point loc, const battle::DeploymentOper& oper, - Point recommended_direction); + struct DirectionAndScore + { + battle::DeployDirection direction; + int score = 0; + }; + DirectionAndScore calc_best_direction_and_score(Point loc, const battle::DeploymentOper& oper, + battle::DeployDirection recommended_direction) const; + + void postproc_of_deployments(const battle::DeploymentOper& oper, const Point& placed_loc, + battle::DeployDirection direction); + + void check_drone_tiles(); + void wait_for_start_button_clicked(); + + /* from config */ + + std::vector m_homes; + bool m_allow_to_use_dice = true; + std::unordered_set m_blacklist_location; + std::array m_role_order {}; + std::unordered_map m_force_deploy_direction; + + struct AirDefenseData + { + /* from config */ + int stop_blocking_deploy_num = INT_MAX; + int deploy_air_defense_num = 0; + bool ban_medic = false; + /* real time */ + int has_deployed_blocking_num = 0; + int has_deployed_air_defense_num = 0; + bool has_finished_deploy_air_defense = false; + }; + AirDefenseData m_force_air_defense; + + /* real time */ + + size_t m_cur_home_index = 0; + bool m_first_deploy = true; + bool m_melee_full = false; + bool m_ranged_full = false; + + struct HomeInfo + { + bool wait_blocking = true; + bool wait_medic = true; + bool indeed_no_medic = false; + Point blocking_pos; + }; + std::vector m_homes_status; + std::unordered_map m_blocking_for_home_index; + std::unordered_map> m_medic_for_home_index; + std::deque m_urgent_home_index; struct DroneTile { @@ -88,61 +136,6 @@ namespace asst } bool operator<(const DroneTile& x) const { return x.placed_time < placed_time; } }; - - bool m_opers_used = false; - bool m_is_cur_urgent = false; - bool m_stage_use_dice = true; - bool m_use_dice = false; - bool m_first_deploy = true; - bool m_melee_full = false; - bool m_ranged_full = false; - int m_last_not_urgent = -1; - int m_pre_hp = 0; - int m_kills = 0; - int m_total_kills = 0; - struct - { - int stop_blocking_deploy_num = INT_MAX; - int has_deployed_blocking_num = 0; - int deploy_air_defense_num = 0; - int has_deployed_air_defense_num = 0; - bool has_finished_deploy_air_defense = false; - bool ban_medic = false; - - void clear() noexcept - { - stop_blocking_deploy_num = INT_MAX; - deploy_air_defense_num = 0; - has_deployed_blocking_num = 0; - has_deployed_air_defense_num = 0; - has_finished_deploy_air_defense = false; - ban_medic = false; - } - } m_force_air_defense; - - int m_last_cooling_count = 0; - size_t m_cur_home_index = 0; - cv::Mat m_dice_image; - - std::array m_role_order; - std::unordered_map m_side_tile_info; - std::unordered_map m_normal_tile_info; - std::vector m_homes; - std::vector m_wait_blocking; - std::vector m_wait_medic; - std::vector m_indeed_no_medic; - std::unordered_map m_blocking_for_home_index; - std::unordered_map> m_medic_for_home_index; - std::stack m_next_urgent_index; - std::unordered_set m_blacklist_location; - std::set m_retreated_opers; - std::queue m_key_kills; - std::unordered_map m_used_tiles; - std::unordered_map m_opers_in_field; - std::unordered_map m_restore_status; std::priority_queue m_need_clear_tiles; - std::unordered_map m_force_deploy_direction; - - std::string m_stage_name; }; } // namespace asst diff --git a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h index e17170f317..f39d551025 100644 --- a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h @@ -18,8 +18,6 @@ namespace asst Kills = 16, // 击杀数 Cost = 32, // 费用 Vacancies = 64, // 剩余可部署干员数 - // 肉鸽模式需要用到的识别 - Roguelike = Oper }; public: