diff --git a/resource/copilot/SSS_Test.json b/resource/copilot/SSS_Test.json index 0894652d7d..32e4223b1a 100644 --- a/resource/copilot/SSS_Test.json +++ b/resource/copilot/SSS_Test.json @@ -46,7 +46,7 @@ "能天使", "Pioneer", "Support", - "None" + "无需增调干员" ], "stages": [ { diff --git a/resource/tasks.json b/resource/tasks.json index 95ee67c1d0..7bc65d6cae 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -9546,5 +9546,83 @@ 129, 157 ] + }, + "SSSSettlement": { + "action": "ClickSelf", + "roi": [ + 1009, + 0, + 155, + 143 + ], + "next": [ + "SSSSettlementConfirm" + ] + }, + "SSSSettlementConfirm": { + "action": "ClickSelf", + "roi": [ + 756, + 414, + 174, + 160 + ] + }, + "SSSComfirmBattleComplete": { + "algorithm": "JustReturn", + "action": "DoNothing", + "next": [ + "SSSDropRecruitmentFlag", + "SSSStartFighting", + "SSSClickUntilStartScreen" + ] + }, + "SSSDropRecruitmentFlag": { + "action": "DoNothing", + "roi": [ + 0, + 39, + 250, + 203 + ] + }, + "SSSClickUntilStartScreen": { + "algorithm": "JustReturn", + "action": "ClickRect", + "specificRect": [ + 529, + 27, + 227, + 126 + ], + "next": [ + "SSSComfirmBattleComplete" + ] + }, + "SSSDropRecruitmentOCR": { + "algorithm": "OcrDetect", + "action": "DoNothing", + "text": [], + "cache": false, + "roi": [ + 0, + 434, + 1280, + 169 + ] + }, + "SSSDropRecruitmentConfrim": { + "algorithm": "OcrDetect", + "action": "ClickSelf", + "text": [ + "确认调用", + "放弃调用" + ], + "roi": [ + 1105, + 525, + 175, + 132 + ] } } diff --git a/resource/template/SSSDropRecruitmentFlag.png b/resource/template/SSSDropRecruitmentFlag.png new file mode 100644 index 0000000000..300c7920f5 Binary files /dev/null and b/resource/template/SSSDropRecruitmentFlag.png differ diff --git a/resource/template/SSSSettlement.png b/resource/template/SSSSettlement.png new file mode 100644 index 0000000000..7543a2f90b Binary files /dev/null and b/resource/template/SSSSettlement.png differ diff --git a/resource/template/SSSSettlementConfirm.png b/resource/template/SSSSettlementConfirm.png new file mode 100644 index 0000000000..ef5dbbbfef Binary files /dev/null and b/resource/template/SSSSettlementConfirm.png differ diff --git a/src/MaaCore/Config/Miscellaneous/SSSCopilotConfig.cpp b/src/MaaCore/Config/Miscellaneous/SSSCopilotConfig.cpp index 8e033feece..de87697ab9 100644 --- a/src/MaaCore/Config/Miscellaneous/SSSCopilotConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/SSSCopilotConfig.cpp @@ -61,8 +61,7 @@ bool asst::SSSCopilotConfig::parse(const json::value& json) m_data.drop_tool_men.emplace_back(name); } else { - Log.error("Unknown drop tool man", name); - return false; + Log.warn("Unknown drop tool man", name); } } } diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index f4154ee3c0..74ec257fac 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -36,6 +36,7 @@ void asst::BattleHelper::clear() m_normal_tile_info.clear(); m_skill_usage.clear(); + m_in_battle = false; m_kills = 0; m_total_kills = 0; m_all_deployment_avatars.clear(); @@ -260,6 +261,7 @@ bool asst::BattleHelper::update_deployment(bool init, const cv::Mat& reusable) cancel_oper_selection(); } } + check_pause_button(image); return true; } @@ -404,11 +406,14 @@ bool asst::BattleHelper::use_skill(const Point& loc, bool keep_waiting) return click_oper_on_battlefield(loc) && click_skill(keep_waiting); } -bool asst::BattleHelper::check_pause_button() +bool asst::BattleHelper::check_pause_button(const cv::Mat& reusable) { - MatchImageAnalyzer battle_flag_analyzer(m_inst_helper.ctrler()->get_image()); + cv::Mat image = reusable.empty() ? m_inst_helper.ctrler()->get_image() : reusable; + MatchImageAnalyzer battle_flag_analyzer(image); battle_flag_analyzer.set_task_info("BattleOfficiallyBegin"); - return battle_flag_analyzer.analyze(); + bool ret = battle_flag_analyzer.analyze(); + m_in_battle = ret; + return ret; } bool asst::BattleHelper::wait_until_start() @@ -434,6 +439,7 @@ bool asst::BattleHelper::wait_until_end() bool asst::BattleHelper::do_strategic_action(const cv::Mat& reusable) { + check_pause_button(reusable); return use_all_ready_skill(reusable); } diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h index 715c93d2fc..b65a18c74e 100644 --- a/src/MaaCore/Task/BattleHelper.h +++ b/src/MaaCore/Task/BattleHelper.h @@ -48,7 +48,7 @@ namespace asst bool retreat_oper(const Point& loc, bool manually = true); bool use_skill(const std::string& name, bool keep_waiting = true); bool use_skill(const Point& loc, bool keep_waiting = true); - bool check_pause_button(); + bool check_pause_button(const cv::Mat& reusable = cv::Mat()); virtual bool wait_until_start(); bool wait_until_end(); bool use_all_ready_skill(const cv::Mat& reusable = cv::Mat()); @@ -74,6 +74,7 @@ namespace asst std::unordered_map m_skill_usage; /* 实时更新的数据 */ + bool m_in_battle = false; int m_kills = 0; int m_total_kills = 0; int m_cost = 0; diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 2b2a3a1b6d..5ba077d465 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -41,7 +41,7 @@ bool asst::BattleProcessTask::_run() update_deployment(true); to_group(); - for (size_t i = 0; i < get_combat_data().actions.size() && !need_exit(); ++i) { + for (size_t i = 0; i < get_combat_data().actions.size() && !need_exit() && m_in_battle; ++i) { do_action(i); } @@ -159,7 +159,7 @@ bool asst::BattleProcessTask::do_action(size_t action_index) } bool ret = false; - const std::string& name = m_oper_in_group[action.name]; + const std::string& name = get_name_from_group(action.name); const auto& location = action.location; switch (action.type) { @@ -210,6 +210,16 @@ bool asst::BattleProcessTask::do_action(size_t action_index) return ret; } +const std::string& asst::BattleProcessTask::get_name_from_group(const std::string& action_name) +{ + auto iter = m_oper_in_group.find(action_name); + if (iter != m_oper_in_group.cend()) { + return iter->second; + } + m_oper_in_group.emplace(action_name, action_name); + return m_oper_in_group.at(action_name); +} + void asst::BattleProcessTask::notify_action(const battle::copilot::Action& action) { const static std::unordered_map ActionNames = { @@ -245,7 +255,7 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) update_cost(image); int pre_cost = m_cost; - do { + while (!need_exit() && m_in_battle) { update_cost(image); if (action.cost_changes != 0) { if ((pre_cost + action.cost_changes < 0) ? (m_cost <= pre_cost + action.cost_changes) @@ -254,7 +264,7 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) } } do_strategy_and_update_image(); - } while (!need_exit()); + } } if (m_kills < action.kills) { @@ -270,7 +280,7 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) if (action.costs) { update_image_if_empty(); - while (!need_exit()) { + while (!need_exit() && m_in_battle) { update_cost(image); if (m_cost >= action.costs) { break; @@ -282,7 +292,7 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) // 计算有几个干员在cd if (action.cooling >= 0) { update_image_if_empty(); - while (!need_exit()) { + while (!need_exit() && m_in_battle) { update_deployment(false, image); size_t cooling_count = ranges::count_if(m_cur_deployment_opers | views::values, [](const auto& oper) -> bool { return oper.cooling; }); @@ -295,9 +305,9 @@ bool asst::BattleProcessTask::wait_condition(const Action& action) // 部署干员还有额外等待费用够或 CD 转好 if (!m_in_bullet_time && action.type == ActionType::Deploy) { - const std::string& name = m_oper_in_group[action.name]; + const std::string& name = get_name_from_group(action.name); update_image_if_empty(); - while (!need_exit()) { + while (!need_exit() && m_in_battle) { update_deployment(false, image); if (auto iter = m_cur_deployment_opers.find(name); iter != m_cur_deployment_opers.cend() && iter->second.available) { diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h index d79ae23701..f54cd82464 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h @@ -30,6 +30,7 @@ namespace asst bool to_group(); bool do_action(size_t action_index); + const std::string& get_name_from_group(const std::string& action_name); void notify_action(const battle::copilot::Action& action); bool wait_condition(const battle::copilot::Action& action); bool enter_bullet_time_for_next_action(size_t next_index, const Point& location, const std::string& name); diff --git a/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.cpp index 5a196a70d0..52fcaba16e 100644 --- a/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.cpp @@ -19,6 +19,11 @@ bool asst::SSSBattleProcessTask::set_stage_index(size_t index) m_sss_combat_data = SSSCopilot.get_data(index); ranges::transform(m_sss_combat_data.strategies, std::inserter(m_all_cores, m_all_cores.begin()), [](const auto& strategy) { return strategy.core; }); + for (const auto& action : m_sss_combat_data.actions) { + if (action.type == battle::copilot::ActionType::Deploy) { + m_all_action_opers.emplace(action.name); + } + } if (!BattleHelper::set_stage_name(m_sss_combat_data.info.stage_name)) { json::value info = basic_info_with_what("UnsupportedLevel"); @@ -39,7 +44,7 @@ bool asst::SSSBattleProcessTask::do_derived_action(size_t action_index) switch (action.type) { case battle::copilot::ActionType::DrawCard: - return draw_card(true); + return draw_card(); case battle::copilot::ActionType::CheckIfStartOver: return check_if_start_over(action); default: @@ -54,6 +59,9 @@ bool asst::SSSBattleProcessTask::do_strategic_action(const cv::Mat& reusable) cv::Mat image = reusable.empty() ? ctrler()->get_image() : reusable; update_deployment(false, image); + if (!m_in_battle) { + return false; + } std::unordered_map exist_core; std::unordered_map available_tool_men; @@ -91,8 +99,9 @@ bool asst::SSSBattleProcessTask::do_strategic_action(const cv::Mat& reusable) } // for apple-clang build error Role role_for_lambda = role; - auto iter = ranges::find_if(available_tool_men, - [role_for_lambda](const auto& pair) { return pair.first == role_for_lambda; }); + auto iter = ranges::find_if(available_tool_men, [&](const auto& pair) { + return pair.first == role_for_lambda && !m_all_action_opers.contains(pair.second.name); + }); if (iter == available_tool_men.end()) { continue; } @@ -104,7 +113,7 @@ bool asst::SSSBattleProcessTask::do_strategic_action(const cv::Mat& reusable) } if (m_sss_combat_data.draw_as_possible) { - draw_card(); + draw_card(false); } use_all_ready_skill(image); diff --git a/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.h index ee990c6419..105004435b 100644 --- a/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/SSSBattleProcessTask.h @@ -24,6 +24,7 @@ namespace asst battle::sss::CombatData m_sss_combat_data; std::unordered_set m_all_cores; + std::unordered_set m_all_action_opers; private: using BattleProcessTask::set_stage_name; diff --git a/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.cpp b/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.cpp index 79bb46d128..08f86a91c1 100644 --- a/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.cpp @@ -1,10 +1,12 @@ #include "SSSStageManagerTask.h" +#include "Config/Miscellaneous/BattleDataConfig.h" #include "Config/Miscellaneous/SSSCopilotConfig.h" #include "Controller.h" #include "Task/Miscellaneous/SSSBattleProcessTask.h" #include "Task/ProcessTask.h" #include "Utils/Logger.hpp" +#include "Vision/OcrImageAnalyzer.h" #include "Vision/OcrWithPreprocessImageAnalyzer.h" bool asst::SSSStageManagerTask::_run() @@ -23,9 +25,6 @@ bool asst::SSSStageManagerTask::_run() return false; } - if (!click_start_button()) { - return false; - } const auto& [stage_name, stage_index] = stage_opt.value(); int times = m_remaining_times[stage_name] + 1; @@ -33,12 +32,16 @@ bool asst::SSSStageManagerTask::_run() battle_task.set_stage_index(stage_index); for (int i = 0; i < times; ++i) { - if (battle_task.run()) { + if (click_start_button() && battle_task.run() && !need_exit()) { break; } } + + do { + } while (!comfirm_battle_complete() && !need_exit()); + if (stage_index >= stage_count) { - // TODO: 任务结束 + settlement(); break; } } @@ -74,7 +77,70 @@ std::optional asst::SSSStageManagerTask::a return std::nullopt; } +bool asst::SSSStageManagerTask::comfirm_battle_complete() +{ + ProcessTask task(*this, { "SSSComfirmBattleComplete" }); + task.set_times_limit("SSSStartFighting", 0); + if (!task.run()) { + return false; + } + + if (const std::string& last = task.get_last_task_name(); last == "SSSStartFighting") { + return true; + } + else if (last == "SSSDropRecruitmentFlag") { + get_drop_rewards(); + return false; + } + return false; +} + +bool asst::SSSStageManagerTask::get_drop_rewards() +{ + using namespace battle; + LogTraceFunction; + + OcrImageAnalyzer analyzer(ctrler()->get_image()); + analyzer.set_task_info("SSSDropRecruitmentOCR"); + if (!analyzer.analyze()) { + Log.error(__FUNCTION__, "OCR failed to analyze"); + return false; + } + + struct DropRecruitment + { + TextRect ocr_res; + std::optional role; + }; + + std::vector opers; + for (const auto& result : analyzer.get_result()) { + auto role = BattleData.get_role(result.text); + opers.emplace_back(DropRecruitment { + .ocr_res = result, .role = role == Role::Unknown ? std::nullopt : std::optional(role) }); + } + + for (const std::string& name : SSSCopilot.get_data().drop_tool_men) { + auto role = get_role_type(name); + bool is_role = role != Role::Unknown; + auto iter = ranges::find_if(opers, [&](const DropRecruitment& props) { + return (is_role && props.role) ? *props.role == role : props.ocr_res.text == name; + }); + if (iter != opers.cend()) { + ctrler()->click(iter->ocr_res.rect); + break; + } + } + + return ProcessTask(*this, { "SSSDropRecruitmentConfrim" }).run(); +} + bool asst::SSSStageManagerTask::click_start_button() { return ProcessTask(*this, { "SSSStartFighting", "SSSCloseTip" }).run(); } + +bool asst::SSSStageManagerTask::settlement() +{ + return ProcessTask(*this, { "SSSSettlement" }).run(); +} diff --git a/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.h b/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.h index d321109025..9cbfa438bb 100644 --- a/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.h +++ b/src/MaaCore/Task/Miscellaneous/SSSStageManagerTask.h @@ -22,7 +22,10 @@ namespace asst int index = 0; }; std::optional analyze_stage(); + bool comfirm_battle_complete(); + bool get_drop_rewards(); bool click_start_button(); + bool settlement(); // 结算奖励(退出整局保全战斗) std::unordered_map m_remaining_times; }; diff --git a/src/MaaCore/Task/ProcessTask.cpp b/src/MaaCore/Task/ProcessTask.cpp index 8936f9ce66..6cd6cef9ba 100644 --- a/src/MaaCore/Task/ProcessTask.cpp +++ b/src/MaaCore/Task/ProcessTask.cpp @@ -16,14 +16,14 @@ using namespace asst; asst::ProcessTask::ProcessTask(const AbstractTask& abs, std::vector tasks_name) - : AbstractTask(abs), m_raw_tasks_name(std::move(tasks_name)) + : AbstractTask(abs), m_raw_task_name_list(std::move(tasks_name)) { m_task_delay = Config.get_options().task_delay; m_basic_info_cache = json::value(); } asst::ProcessTask::ProcessTask(AbstractTask&& abs, std::vector tasks_name) noexcept - : AbstractTask(std::move(abs)), m_raw_tasks_name(std::move(tasks_name)) + : AbstractTask(std::move(abs)), m_raw_task_name_list(std::move(tasks_name)) { m_task_delay = Config.get_options().task_delay; m_basic_info_cache = json::value(); @@ -39,7 +39,7 @@ bool asst::ProcessTask::run() m_task_delay = Config.get_options().task_delay; } - m_cur_tasks_name = m_raw_tasks_name; + m_cur_task_name_list = m_raw_task_name_list; for (m_cur_retry = 0; m_cur_retry <= m_retry_times; ++m_cur_retry) { if (_run()) { return true; @@ -62,7 +62,7 @@ ProcessTask& asst::ProcessTask::set_task_delay(int delay) noexcept asst::ProcessTask& asst::ProcessTask::set_tasks(std::vector tasks_name) noexcept { m_cur_retry = 0; - m_raw_tasks_name = std::move(tasks_name); + m_raw_task_name_list = std::move(tasks_name); m_pre_task_name.clear(); return *this; } @@ -83,7 +83,7 @@ bool ProcessTask::_run() { LogTraceFunction; - while (!m_cur_tasks_name.empty()) { + while (!m_cur_task_name_list.empty()) { if (need_exit()) { return false; } @@ -93,16 +93,16 @@ bool ProcessTask::_run() json::value info = basic_info(); info["details"] = json::object { - { "to_be_recognized", json::array(m_cur_tasks_name) }, + { "to_be_recognized", json::array(m_cur_task_name_list) }, { "cur_retry", m_cur_retry }, { "retry_times", m_retry_times }, }; Log.info(info.to_string()); - auto front_task_ptr = Task.get(m_cur_tasks_name.front()); + auto front_task_ptr = Task.get(m_cur_task_name_list.front()); // 可能有配置错误,导致不存在对应的任务 if (front_task_ptr == nullptr) { - Log.error("Invalid task", m_cur_tasks_name.front()); + Log.error("Invalid task", m_cur_task_name_list.front()); return false; } @@ -113,7 +113,7 @@ bool ProcessTask::_run() } else { const auto image = ctrler()->get_image(); - ProcessTaskImageAnalyzer analyzer(image, m_cur_tasks_name, m_inst); + ProcessTaskImageAnalyzer analyzer(image, m_cur_task_name_list, m_inst); if (!analyzer.analyze()) { return false; @@ -124,28 +124,28 @@ bool ProcessTask::_run() if (need_exit()) { return false; } - std::string cur_name = m_cur_task_ptr->name; + m_last_task_name = m_cur_task_ptr->name; const auto& res_move = m_cur_task_ptr->rect_move; if (!res_move.empty()) { rect = rect.move(res_move); } - int& exec_times = m_exec_times[cur_name]; + int& exec_times = m_exec_times[m_last_task_name]; auto [max_times, limit_type] = calc_time_limit(); if (limit_type == TimesLimitType::Pre && exec_times >= max_times) { info["what"] = "ExceededLimit"; info["details"] = json::object { - { "task", cur_name }, + { "task", m_last_task_name }, { "exec_times", exec_times }, { "max_times", max_times }, { "limit_type", "pre" }, }; Log.info("exec times exceeded the limit", info.to_string()); callback(AsstMsg::SubTaskExtraInfo, info); - m_cur_tasks_name = m_cur_task_ptr->exceeded_next; + m_cur_task_name_list = m_cur_task_ptr->exceeded_next; sleep(m_task_delay); continue; } @@ -154,7 +154,7 @@ bool ProcessTask::_run() ++exec_times; info["details"] = json::object { - { "task", cur_name }, + { "task", m_last_task_name }, { "action", enum_to_string(m_cur_task_ptr->action) }, { "exec_times", exec_times }, { "max_times", max_times }, @@ -198,7 +198,7 @@ bool ProcessTask::_run() break; } - status()->set_number(Status::ProcessTaskLastTimePrefix + cur_name, time(nullptr)); + status()->set_number(Status::ProcessTaskLastTimePrefix + m_last_task_name, time(nullptr)); // 减少其他任务的执行次数 // 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效 @@ -237,14 +237,14 @@ bool ProcessTask::_run() if (limit_type == TimesLimitType::Post && exec_times >= max_times) { info["what"] = "ExceededLimit"; info["details"] = json::object { - { "task", cur_name }, + { "task", m_last_task_name }, { "exec_times", exec_times }, { "max_times", max_times }, { "limit_type", "post" }, }; Log.info("exec times exceeded the limit", info.to_string()); callback(AsstMsg::SubTaskExtraInfo, info); - m_cur_tasks_name = m_cur_task_ptr->exceeded_next; + m_cur_task_name_list = m_cur_task_ptr->exceeded_next; sleep(m_task_delay); continue; } @@ -256,7 +256,7 @@ bool ProcessTask::_run() if (need_stop) { return true; } - m_cur_tasks_name = m_cur_task_ptr->next; + m_cur_task_name_list = m_cur_task_ptr->next; sleep(m_task_delay); } @@ -308,7 +308,7 @@ int asst::ProcessTask::calc_post_delay() const json::value asst::ProcessTask::basic_info() const { return AbstractTask::basic_info() | - json::object { { "first", json::array(m_raw_tasks_name) }, { "pre_task", m_pre_task_name } }; + json::object { { "first", json::array(m_raw_task_name_list) }, { "pre_task", m_pre_task_name } }; } void ProcessTask::exec_click_task(const Rect& matched_rect) diff --git a/src/MaaCore/Task/ProcessTask.h b/src/MaaCore/Task/ProcessTask.h index 7816305ba2..18e4406b6e 100644 --- a/src/MaaCore/Task/ProcessTask.h +++ b/src/MaaCore/Task/ProcessTask.h @@ -35,6 +35,8 @@ namespace asst ProcessTask& set_times_limit(std::string name, int limit, TimesLimitType type = TimesLimitType::Pre); ProcessTask& set_post_delay(std::string name, int delay); + const std::string& get_last_task_name() const noexcept { return m_last_task_name; } + protected: virtual bool _run() override; virtual bool on_run_fails() override; @@ -48,9 +50,10 @@ namespace asst double slope_out); std::shared_ptr m_cur_task_ptr = nullptr; - std::vector m_raw_tasks_name; - std::vector m_cur_tasks_name; + std::vector m_raw_task_name_list; + std::vector m_cur_task_name_list; std::string m_pre_task_name; + std::string m_last_task_name; std::unordered_map m_post_delay; std::unordered_map m_times_limit; std::unordered_map m_exec_times;