diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index b87785edd8..af256c0652 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -220,11 +221,13 @@ struct TriggerInfo }; Category category = Category::None; + int kills = DEACTIVE_KILLS; // 击杀数条件 int costs = DEACTIVE_COST; // 费用条件 int cost_changes = DEACTIVE_COST_CHANGES; // 费用变化条件 int cooling = DEACTIVE_COOLING; // 冷却中的干员条件 int count = DEACTIVE_COUNT; // 计数条件,不做变化,记录初始值 + mutable int counter = 0; // 计数器 // 触发器是否被激活 @@ -314,10 +317,31 @@ struct CheckInfo // 定义until操作需要的信息 struct UntilInfo { - TriggerInfo::Category category; // all 全部命令执行完毕后才结束, any 只要有一个执行就结束 + TriggerInfo::Category mode; // all 全部命令执行完毕后才结束, any 只要有一个执行就结束 std::vector candidate; // 备用的命令序列 }; +// 定义point操作需要的信息 +struct PointInfo +{ + struct SnapShot + { + int kills = TriggerInfo::DEACTIVE_KILLS; + int cost = TriggerInfo::DEACTIVE_COST; + int64_t cooling_count = TriggerInfo::DEACTIVE_COOLING; + int interval = 0; + + std::chrono::steady_clock::time_point tNow; + }; + + std::string target_code; + TriggerInfo::Category mode; + std::pair range; + + std::vector then_actions; // 当条件满足时执行 + std::vector else_actions; // 当条件不满足时执行 +}; + struct CheckIfStartOverInfo { std::string name; @@ -363,7 +387,8 @@ struct Action CaseInfo, LoopInfo, CheckInfo, - UntilInfo> + UntilInfo, + PointInfo> payload; // 信息载荷 // 是否携带干员信息 diff --git a/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp b/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp index fe43fc2afa..74542f7580 100644 --- a/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp @@ -224,6 +224,22 @@ bool asst::CopilotConfig::parse_action(const json::value& action_info, asst::bat { "until", ActionType::Until }, { "UNTIL", ActionType::Until }, { "直到", ActionType::Until }, + + { "SavePoint", ActionType::SavePoint }, + { "savepoint", ActionType::SavePoint }, + { "SAVEPOINT", ActionType::SavePoint }, + { "锚点", ActionType::SavePoint }, + { "保存锚点", ActionType::SavePoint }, + + { "SyncPoint", ActionType::SyncPoint }, + { "syncpoint", ActionType::SyncPoint }, + { "SYNCPOINT", ActionType::SyncPoint }, + { "同步锚点", ActionType::SyncPoint }, + + { "CheckPoint", ActionType::CheckPoint }, + { "checkpoint", ActionType::CheckPoint }, + { "CHECKPOINT", ActionType::CheckPoint }, + { "检查锚点", ActionType::CheckPoint }, }; auto& action = (*_Out); @@ -361,13 +377,13 @@ bool asst::CopilotConfig::parse_action(const json::value& action_info, asst::bat auto& until = action.payload.emplace(); // 必选字段 - until.category = TriggerInfo::loadCategoryFrom(action_info.at("mode").as_string()); - switch (until.category) { + until.mode = TriggerInfo::loadCategoryFrom(action_info.at("mode").as_string()); + switch (until.mode) { case TriggerInfo::Category::Any: case TriggerInfo::Category::All: break; default: - until.category = TriggerInfo::Category::All; + until.mode = TriggerInfo::Category::All; break; } @@ -379,13 +395,64 @@ bool asst::CopilotConfig::parse_action(const json::value& action_info, asst::bat until.candidate = parse_actions_ptr(t.value()); } } break; + case ActionType::SyncPoint: + case ActionType::CheckPoint: { + auto& point = action.payload.emplace(); + + point.target_code = action_info.at("target_code").as_string(); + + point.mode = TriggerInfo::loadCategoryFrom(action_info.at("mode").as_string()); + switch (point.mode) { + case TriggerInfo::Category::Any: + case TriggerInfo::Category::All: + case TriggerInfo::Category::Not: + case TriggerInfo::Category::Succ: + break; + default: + point.mode = TriggerInfo::Category::All; + break; + } + + if (auto t = action_info.find("kill_range")) { + auto range = t.value().as_array(); + point.range.first.kills = range[0].as_integer(); + point.range.second.kills = range[1].as_integer(); + } + + if (auto t = action_info.find("cost_range")) { + auto range = t.value().as_array(); + point.range.first.cost = range[0].as_integer(); + point.range.second.cost = range[1].as_integer(); + } + + if (auto t = action_info.find("cooling_range")) { + auto range = t.value().as_array(); + point.range.first.cooling_count = range[0].as_integer(); + point.range.second.cooling_count = range[1].as_integer(); + } + + if (auto t = action_info.find("time_range")) { + auto range = t.value().as_array(); + point.range.first.interval = range[0].as_integer(); + point.range.second.interval = range[1].as_integer(); + } + + if (auto t = action_info.find("then_actions")) { + point.then_actions = parse_actions_ptr(t.value()); + } + + if (action.type == ActionType::CheckPoint) { + if (auto t = action_info.find("else_actions")) { + point.else_actions = parse_actions_ptr(t.value()); + } + } + + } break; case ActionType::SwitchSpeed: case ActionType::Output: case ActionType::SkillDaemon: case ActionType::DrawCard: case ActionType::SavePoint: - case ActionType::SyncPoint: - case ActionType::CheckPoint: [[fallthrough]]; default: break; diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h index 989baca17d..faa11ab878 100644 --- a/src/MaaCore/Task/BattleHelper.h +++ b/src/MaaCore/Task/BattleHelper.h @@ -96,6 +96,7 @@ protected: int m_total_kills = 0; int m_kills = 0; int m_cost = 0; + int m_cooling_count = 0; std::vector m_cur_deployment_opers; diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 3efefa90f2..a81b26e6f8 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -178,6 +178,9 @@ bool asst::BattleProcessTask::do_action(const battle::copilot::Action& action, s update_deployment(); } + // 如果携带锚点编码,则存储快照 + save_snap_shot(action.point_code); + bool ret = false; switch (action.type) { @@ -315,7 +318,7 @@ END_LOOP:; // 循环遍历携带的所有子动作,只要成功一个才退出 // 防止死循环,添加loop_limit参数来限制循环 action.trigger.resetCounter(); - switch (info.category) { + switch (info.mode) { case TriggerInfo::Category::Any: { int idx = 0; while (ret == false) { @@ -411,6 +414,75 @@ END_LOOP:; } } break; + case ActionType::SavePoint: + ret = true; // 锚点保存动作由于携带锚点编码,已经在前面进行存储 + break; + case ActionType::SyncPoint: { + auto& info = action.getPayload(); + + thread_local auto prev_frame_time = std::chrono::steady_clock::time_point {}; + static const auto min_frame_interval = + std::chrono::milliseconds(Config.get_options().copilot_fight_screencap_interval); + + do { + const auto now = std::chrono::steady_clock::now(); + + if (check_point(info, now)) { + break; + } + + // prevent our program from consuming too much CPU + if (prev_frame_time > now - min_frame_interval) [[unlikely]] { + Log.debug("Sleeping for framerate limit"); + std::this_thread::sleep_for(min_frame_interval - (now - prev_frame_time)); + } + } while (!need_exit() && m_in_battle); + + int i = 0; + for (; i < info.then_actions.size(); ++i) { + if (!do_action_sync(*info.then_actions[i], i)) { + break; + } + + if (need_exit() || !m_in_battle) { + break; + } + } + + ret = (i == info.then_actions.size()); + } break; + case ActionType::CheckPoint: { + auto& info = action.getPayload(); + + if (check_point(info, std::chrono::steady_clock::now())) { + int i = 0; + for (; i < info.then_actions.size(); ++i) { + if (!do_action_sync(*info.then_actions[i], i)) { + break; + } + + if (need_exit() || !m_in_battle) { + break; + } + } + + ret = (i == info.then_actions.size()); + } + else { + int i = 0; + for (; i < info.else_actions.size(); ++i) { + if (!do_action_sync(*info.else_actions[i], i)) { + break; + } + + if (need_exit() || !m_in_battle) { + break; + } + } + + ret = (i == info.then_actions.size()); + } + } break; default: ret = do_derived_action(action, index); break; @@ -489,7 +561,7 @@ bool asst::BattleProcessTask::do_action_async(const battle::copilot::Action& act } // 所有被设置的触发器都满足, 不等待 - if (check_condition(action.trigger)) { + if (!check_condition(action.trigger)) { return false; } @@ -533,6 +605,9 @@ void asst::BattleProcessTask::notify_action(const battle::copilot::Action& actio { ActionType::Case, "Case" }, { ActionType::Check, "Check" }, { ActionType::Until, "Until" }, + { ActionType::SavePoint, "SavePoint" }, + { ActionType::SyncPoint, "SyncPoint" }, + { ActionType::CheckPoint, "CheckPoint" }, }; json::value info = basic_info_with_what("CopilotAction"); @@ -991,19 +1066,19 @@ bool asst::BattleProcessTask::check_condition(const battle::copilot::TriggerInfo case TriggerInfo::Category::Succ: // 默认成功,跳过条件阶段,什么都不用做 break; case TriggerInfo::Category::Any: { - if (check_condition_any(_Trigger)) { + if (!check_condition_any(_Trigger)) { return false; } } break; case TriggerInfo::Category::Not: { - if (check_condition_not(_Trigger)) { + if (!check_condition_not(_Trigger)) { return false; } } break; case TriggerInfo::Category::All: [[fallthrough]]; default: { - if (check_condition_all(_Trigger)) { + if (!check_condition_all(_Trigger)) { return false; } } break; @@ -1012,6 +1087,158 @@ bool asst::BattleProcessTask::check_condition(const battle::copilot::TriggerInfo return true; } +bool asst::BattleProcessTask::check_point( + const battle::copilot::PointInfo& _Current, + std::chrono::steady_clock::time_point const& _Now) +{ + switch (_Current.mode) { + case TriggerInfo::Category::Succ: // 默认成功,跳过条件阶段,什么都不用做 + break; + case TriggerInfo::Category::Any: { + if (!check_point_any(_Current, _Now)) { + return false; + } + } break; + case TriggerInfo::Category::Not: { + if (!check_point_not(_Current, _Now)) { + return false; + } + } break; + case TriggerInfo::Category::All: + [[fallthrough]]; + default: { + if (!check_point_all(_Current, _Now)) { + return false; + } + } break; + } + + return true; +} + +bool asst::BattleProcessTask::check_point_not( + const battle::copilot::PointInfo& _Current, + std::chrono::steady_clock::time_point const& _Now) +{ + using TriggerInfo = battle::copilot::TriggerInfo; + + auto& target = get_snap_shot(_Current.target_code); + + if (_Current.range.first.kills != TriggerInfo::DEACTIVE_KILLS) { + if ((_Current.range.first.kills <= target.kills) && (target.kills <= _Current.range.second.kills)) { + return false; + } + } + + if (_Current.range.first.cost != TriggerInfo::DEACTIVE_COST) { + if ((_Current.range.first.cost <= target.cost) && (target.cost <= _Current.range.second.cost)) { + return false; + } + } + + if (_Current.range.first.cooling_count != TriggerInfo::DEACTIVE_COST) { + if ((_Current.range.first.cooling_count <= target.cooling_count) && + (target.cooling_count <= _Current.range.second.cooling_count)) { + return false; + } + } + + if (_Current.range.first.interval != 0 || _Current.range.second.interval != 0) { + auto diff = std::chrono::duration_cast(_Now - target.tNow).count(); + if ((_Current.range.first.interval <= diff) && (diff <= _Current.range.second.interval)) { + return false; + } + } + + return true; +} + +bool asst::BattleProcessTask::check_point_all( + const battle::copilot::PointInfo& _Current, + std::chrono::steady_clock::time_point const& _Now) +{ + using TriggerInfo = battle::copilot::TriggerInfo; + + auto& target = get_snap_shot(_Current.target_code); + + if (_Current.range.first.kills != TriggerInfo::DEACTIVE_KILLS) { + if ((_Current.range.first.kills <= target.kills) && (target.kills <= _Current.range.second.kills)) { + ; + } + else { + return false; + } + } + + if (_Current.range.first.cost != TriggerInfo::DEACTIVE_COST) { + if ((_Current.range.first.cost <= target.cost) && (target.cost <= _Current.range.second.cost)) { + ; + } + else { + return false; + } + } + + if (_Current.range.first.cooling_count != TriggerInfo::DEACTIVE_COST) { + if ((_Current.range.first.cooling_count <= target.cooling_count) && + (target.cooling_count <= _Current.range.second.cooling_count)) { + ; + } + else { + return false; + } + } + + if (_Current.range.first.interval != 0 || _Current.range.second.interval != 0) { + auto diff = std::chrono::duration_cast(_Now - target.tNow).count(); + if ((_Current.range.first.interval <= diff) && (diff <= _Current.range.second.interval)) { + ; + } + else { + return false; + } + } + + return true; +} + +bool asst::BattleProcessTask::check_point_any( + const battle::copilot::PointInfo& _Current, + std::chrono::steady_clock::time_point const& _Now) +{ + using TriggerInfo = battle::copilot::TriggerInfo; + + auto& target = get_snap_shot(_Current.target_code); + + if (_Current.range.first.kills != TriggerInfo::DEACTIVE_KILLS) { + if ((_Current.range.first.kills <= target.kills) && (target.kills <= _Current.range.second.kills)) { + return true; + } + } + + if (_Current.range.first.cost != TriggerInfo::DEACTIVE_COST) { + if ((_Current.range.first.cost <= target.cost) && (target.cost <= _Current.range.second.cost)) { + return true; + } + } + + if (_Current.range.first.cooling_count != TriggerInfo::DEACTIVE_COST) { + if ((_Current.range.first.cooling_count <= target.cooling_count) && + (target.cooling_count <= _Current.range.second.cooling_count)) { + return true; + } + } + + if (_Current.range.first.interval != 0 || _Current.range.second.interval != 0) { + auto diff = std::chrono::duration_cast(_Now - target.tNow).count(); + if ((_Current.range.first.interval <= diff) && (diff <= _Current.range.second.interval)) { + return true; + } + } + + return false; +} + bool asst::BattleProcessTask::enter_bullet_time(const std::string& name, const Point& location) { LogTraceFunction; @@ -1038,6 +1265,42 @@ void asst::BattleProcessTask::sleep_and_do_strategy(unsigned millisecond) } } +void asst::BattleProcessTask::save_snap_shot(const std::string& code) +{ + using TriggerInfo = battle::copilot::TriggerInfo; + + if (code.empty()) { + return; + } + + auto& shot = m_snap_shots[code]; + + cv::Mat image; + + update_image_if_empty(&image); + + update_cost(image); + shot.cost = m_cost; + + update_kills(image); + shot.kills = m_kills; + + if (update_deployment(false, image)) { + shot.cooling_count = + ranges::count_if(m_cur_deployment_opers, [](const auto& oper) -> bool { return oper.cooling; }); + } + + shot.tNow = std::chrono::steady_clock::now(); + + do_strategy_and_update_image(&image); +} + +auto asst::BattleProcessTask::get_snap_shot(const std::string& code) const noexcept + -> battle::copilot::PointInfo::SnapShot const& +{ + return m_snap_shots.at(code); +} + bool asst::BattleProcessTask::check_in_battle(const cv::Mat& reusable, bool weak) { LogTraceFunction; diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h index c2b3513223..9aca943cf3 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h @@ -59,13 +59,23 @@ protected: bool check_condition_any(const battle::copilot::TriggerInfo& action); // 某个条件都满足 bool check_condition(const battle::copilot::TriggerInfo& action); + bool check_point(const battle::copilot::PointInfo& _Current, std::chrono::steady_clock::time_point const& _Now); + bool check_point_not(const battle::copilot::PointInfo& _Current, std::chrono::steady_clock::time_point const& _Now); + bool check_point_all(const battle::copilot::PointInfo& _Current, std::chrono::steady_clock::time_point const& _Now); + bool check_point_any(const battle::copilot::PointInfo& _Current, std::chrono::steady_clock::time_point const& _Now); + bool enter_bullet_time(const std::string& name, const Point& location); void sleep_and_do_strategy(unsigned millisecond); + void save_snap_shot(const std::string& code); + + auto get_snap_shot(const std::string& code) const noexcept -> battle::copilot::PointInfo::SnapShot const&; + virtual bool check_in_battle(const cv::Mat& reusable = cv::Mat(), bool weak = true) override; battle::copilot::CombatData m_combat_data; std::unordered_map m_oper_in_group; + std::map m_snap_shots; // 在运行过程中存储的快照信息 bool m_in_bullet_time = false; bool m_need_to_wait_until_end = false;