diff --git a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp index 515030116a..aeaa764150 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp +++ b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp @@ -6,50 +6,94 @@ #include "Utils/Logger.hpp" -const asst::RoguelikeOperInfo& asst::RoguelikeRecruitConfig::get_oper_info(const std::string& theme, - const std::string& name) noexcept +const asst::RoguelikeOperInfo& + asst::RoguelikeRecruitConfig::get_oper_info(const std::string& theme, const std::string& oper_name) noexcept { auto& opers = m_all_opers.at(theme); - if (opers.contains(name)) { - return opers.at(name); + if (opers.contains(oper_name)) { + return opers.at(oper_name); } else { RoguelikeOperInfo info; - info.name = name; - info.group_id = get_group_id(theme, name); - opers.emplace(name, std::move(info)); - return opers.at(name); + info.name = oper_name; + info.group_id = get_group_ids_of_oper(theme, oper_name); + opers.emplace(oper_name, std::move(info)); + return opers.at(oper_name); } } -const std::vector asst::RoguelikeRecruitConfig::get_group_info(const std::string& theme) const noexcept +const asst::RoguelikeGroupInfo& + asst::RoguelikeRecruitConfig::get_group_info(const std::string& theme, const std::string& group_name) noexcept +{ + auto& groups = m_all_groups.at(theme); + if (groups.contains(group_name)) { + return groups.at(group_name); + } + else { + RoguelikeGroupInfo info; + info.name = group_name; + groups.emplace(group_name, std::move(info)); + return groups.at(group_name); + } +} + +const std::vector asst::RoguelikeRecruitConfig::get_group_names(const std::string& theme) const noexcept { return m_oper_groups.at(theme); } -const std::vector asst::RoguelikeRecruitConfig::get_team_complete_info( - const std::string& theme) const noexcept +const std::vector + asst::RoguelikeRecruitConfig::get_team_complete_info(const std::string& theme) const noexcept { - return m_team_complete_comdition.at(theme); + return m_team_complete_condition.at(theme); } -std::vector asst::RoguelikeRecruitConfig::get_group_id(const std::string& theme, - const std::string& name) const noexcept +std::vector asst::RoguelikeRecruitConfig::get_group_ids_of_oper( + const std::string& theme, + const std::string& oper_name) const noexcept { auto& opers = m_all_opers.at(theme); - if (auto find_iter = opers.find(name); find_iter != opers.cend()) { + if (auto find_iter = opers.find(oper_name); find_iter != opers.cend()) { return find_iter->second.group_id; } else { - const auto& role = BattleData.get_role(name); + const auto& role = BattleData.get_role(oper_name); if (role != battle::Role::Pioneer && role != battle::Role::Tank && role != battle::Role::Warrior && - role != battle::Role::Special) + role != battle::Role::Special) { return { static_cast(m_oper_groups.at(theme).size()) - 2 }; - else + } + else { return { static_cast(m_oper_groups.at(theme).size()) - 1 }; + } } } +int asst::RoguelikeRecruitConfig::get_group_id_from_name( + const std::string& theme, + const std::string& group_name) noexcept +{ + auto& groups = m_all_groups.at(theme); + if (groups.contains(group_name)) { + return groups.at(group_name).id; + } + else { + RoguelikeGroupInfo info; + info.name = group_name; + groups.emplace(group_name, std::move(info)); + return groups.at(group_name).id; + } +} + +const std::string + asst::RoguelikeRecruitConfig::get_group_name_from_id(const std::string& theme, const int group_id) const noexcept +{ + auto it = ranges::find_if(m_all_groups.at(theme), [&](const auto& group) { return group.second.id == group_id; }); + if (it != m_all_groups.at(theme).end()) { + return it->first; + } + return ""; +} + bool asst::RoguelikeRecruitConfig::parse(const json::value& json) { LogTraceFunction; @@ -61,52 +105,60 @@ bool asst::RoguelikeRecruitConfig::parse(const json::value& json) int group_id = 0; //{"name":干员组名, "opers":组内干员组成的array} for (const auto& group_json : json.at("priority").as_array()) { - m_oper_groups[theme].emplace_back(group_json.at("name").as_string()); + const std::string group_name = group_json.at("name").as_string(); + m_oper_groups[theme].emplace_back(group_name); + + // 干员组信息 + RoguelikeGroupInfo group_info; + group_info.name = group_name; + group_info.id = group_id; + // 干员在组内的顺序,int int order_in_group = 0; // 遍历"opers"数组 - for (const auto& oper_info : group_json.at("opers").as_array()) { - std::string name = oper_info.at("name").as_string(); + for (const auto& oper_json : group_json.at("opers").as_array()) { + std::string name = oper_json.at("name").as_string(); + group_info.opers.emplace(name); // 肉鸽干员招募信息 - RoguelikeOperInfo info; + RoguelikeOperInfo oper_info; auto iter = m_all_opers[theme].find(name); if (iter != m_all_opers[theme].end()) { // 干员已存在时仅做更新 - info = iter->second; + oper_info = iter->second; } - info.name = name; - info.group_id.push_back(group_id); - info.order_in_group[group_id] = order_in_group; - info.recruit_priority = oper_info.get("recruit_priority", info.recruit_priority); - info.promote_priority = oper_info.get("promote_priority", info.promote_priority); - info.is_alternate = oper_info.get("is_alternate", info.is_alternate); - info.skill = oper_info.get("skill", info.skill); - info.alternate_skill = oper_info.get("alternate_skill", info.alternate_skill); - info.skill_usage = - static_cast(oper_info.get("skill_usage", static_cast(info.skill_usage))); - info.skill_times = oper_info.get("skill_times", info.skill_times); - info.alternate_skill_usage = static_cast( - oper_info.get("alternate_skill_usage", static_cast(info.alternate_skill_usage))); - info.alternate_skill_times = oper_info.get("alternate_skill_times", info.alternate_skill_times); - info.is_key = oper_info.get("is_key", info.is_key); - info.is_start = oper_info.get("is_start", info.is_start); - info.auto_retreat = oper_info.get("auto_retreat", info.auto_retreat); + oper_info.name = name; + oper_info.group_id.push_back(group_id); + oper_info.order_in_group[group_id] = order_in_group; + oper_info.recruit_priority = oper_json.get("recruit_priority", oper_info.recruit_priority); + oper_info.promote_priority = oper_json.get("promote_priority", oper_info.promote_priority); + oper_info.is_alternate = oper_json.get("is_alternate", oper_info.is_alternate); + oper_info.skill = oper_json.get("skill", oper_info.skill); + oper_info.alternate_skill = oper_json.get("alternate_skill", oper_info.alternate_skill); + oper_info.skill_usage = + static_cast(oper_json.get("skill_usage", static_cast(oper_info.skill_usage))); + oper_info.skill_times = oper_json.get("skill_times", oper_info.skill_times); + oper_info.alternate_skill_usage = static_cast( + oper_json.get("alternate_skill_usage", static_cast(oper_info.alternate_skill_usage))); + oper_info.alternate_skill_times = oper_json.get("alternate_skill_times", oper_info.alternate_skill_times); + oper_info.is_key = oper_json.get("is_key", oper_info.is_key); + oper_info.is_start = oper_json.get("is_start", oper_info.is_start); + oper_info.auto_retreat = oper_json.get("auto_retreat", oper_info.auto_retreat); // __________________will-be-removed-begin__________________ - info.recruit_priority_when_team_full = - oper_info.get("recruit_priority_when_team_full", info.recruit_priority - 100); - info.promote_priority_when_team_full = - oper_info.get("promote_priority_when_team_full", info.promote_priority + 300); - if (auto opt = oper_info.find("recruit_priority_offset")) { + oper_info.recruit_priority_when_team_full = + oper_json.get("recruit_priority_when_team_full", oper_info.recruit_priority - 100); + oper_info.promote_priority_when_team_full = + oper_json.get("promote_priority_when_team_full", oper_info.promote_priority + 300); + if (auto opt = oper_json.find("recruit_priority_offset")) { for (const auto& offset : opt.value()) { std::pair offset_pair = std::make_pair(offset[0].as_integer(), offset[1].as_integer()); - info.recruit_priority_offset.emplace_back(offset_pair); + oper_info.recruit_priority_offset.emplace_back(offset_pair); } } - info.offset_melee = oper_info.get("offset_melee", false); + oper_info.offset_melee = oper_json.get("offset_melee", false); // __________________will-be-removed-end__________________ - if (auto opt = oper_info.find("recruit_priority_offsets")) { + if (auto opt = oper_json.find("recruit_priority_offsets")) { for (const auto& offset_json : opt.value()) { RecruitPriorityOffset offset; for (const auto& group : offset_json.at("groups").as_array()) { @@ -115,40 +167,63 @@ bool asst::RoguelikeRecruitConfig::parse(const json::value& json) offset.threshold = offset_json.get("threshold", 0); offset.is_less = offset_json.get("is_less", false); offset.offset = offset_json.get("offset", 0); - info.recruit_priority_offsets.emplace_back(std::move(offset)); + oper_info.recruit_priority_offsets.emplace_back(std::move(offset)); } } - if (auto opt = oper_info.find("collection_priority_offsets")) { + if (auto opt = oper_json.find("collection_priority_offsets")) { for (const auto& offset_json : opt.value()) { CollectionPriorityOffset offset; offset.collection = offset_json.get("collection", ""); offset.offset = offset_json.get("offset", 0); - info.collection_priority_offsets.emplace_back(std::move(offset)); + oper_info.collection_priority_offsets.emplace_back(std::move(offset)); } } - m_all_opers[theme][name] = std::move(info); + m_all_opers[theme][name] = std::move(oper_info); order_in_group++; } + m_all_groups[theme][group_name] = std::move(group_info); group_id++; } + // 对所有存在 offset 组的干员进行初始化 + for (auto& [oper_name, oper_info] : m_all_opers[theme]) { // 所有干员 + if (oper_info.recruit_priority_offsets.empty()) { + continue; + } + for (auto& offset : oper_info.recruit_priority_offsets) { // 干员的所有 offset 策略组 + for (auto& group : offset.groups) { // 策略组内的每个干员组 + offset.opers.insert( // 向当前策略组计入这个干员组的无重复干员 + get_group_info(theme, group).opers.begin(), + get_group_info(theme, group).opers.end()); + } + } + } + for (const auto& condition_json : json.at("team_complete_condition").as_array()) { RecruitPriorityOffset condition; for (const auto& group : condition_json.at("groups").as_array()) { - condition.groups.emplace_back(group.as_string()); + std::string group_name = group.as_string(); + condition.groups.emplace_back(group_name); + + condition.opers.insert( // 向当前策略组计入这个干员组的无重复干员 + get_group_info(theme, group_name).opers.begin(), + get_group_info(theme, group_name).opers.end()); } condition.threshold = condition_json.at("threshold").as_integer(); - m_team_complete_comdition[theme].emplace_back(std::move(condition)); + m_team_complete_require[theme] += condition.threshold; + m_team_complete_condition[theme].emplace_back(std::move(condition)); } return true; } -void asst::RoguelikeRecruitConfig::clear(const std::string& key) +void asst::RoguelikeRecruitConfig::clear(const std::string& theme) { - m_all_opers.erase(key); - m_oper_groups.erase(key); - m_team_complete_comdition.erase(key); + m_all_opers.erase(theme); + m_all_groups.erase(theme); + m_oper_groups.erase(theme); + m_team_complete_condition.erase(theme); + m_team_complete_require.erase(theme); } diff --git a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h index fbfd22f4bc..06cdac4f75 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h +++ b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h @@ -11,67 +11,97 @@ namespace asst { - // 招募priority影响因子 - struct RecruitPriorityOffset +// 招募 priority 影响因子 +struct RecruitPriorityOffset +{ + std::vector groups; // 造成优先级改变的所有干员组 + std::unordered_set opers; // 所有干员组的所有无重复干员 + int threshold = 0; // 已拥有组内干员数量阈值,达到此阈值时应用优先级改变 + bool is_less = false; // 高于阈值时改变还是低于阈值时改变 + int offset = 0; // 优先级改变的大小 +}; + +// 收藏品 priority 影响因子 +struct CollectionPriorityOffset +{ + std::string collection; // 造成优先级改变的藏品名称 + int offset = 0; // 优先级改变的大小 +}; + +// 干员信息,战斗相关 +struct RoguelikeOperInfo +{ + std::string name; // 干员名 + std::vector group_id = {}; // 干员组 id,允许一个干员存在于多个干员组 + std::unordered_map order_in_group; // 干员在干员组内的顺序 干员组 id:干员组内顺序 + int recruit_priority = 0; // 招募优先级 (0-1000) + int promote_priority = 0; // 晋升优先级 (0-1000) + int recruit_priority_when_team_full = 0; // 队伍满时的招募优先级 (0-1000) + int promote_priority_when_team_full = 0; // 队伍满时的晋升优先级 (0-1000) + std::vector> recruit_priority_offset; // [deprecated] + bool offset_melee = false; // [deprecated] + bool is_key = false; // 是否为核心干员 + bool is_start = false; // 是否为开局干员 + std::vector recruit_priority_offsets; // 招募 priority 影响因子 + std::vector collection_priority_offsets; // 收藏品 priority 影响因子 + bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动) + int skill = 0; // 使用几技能 + int alternate_skill = 0; // 当没有指定技能时使用的备选技能,一般是6星干员未精二且精二后使用3技能时才需要指定 + battle::SkillUsage skill_usage = battle::SkillUsage::Possibly; // 技能使用模式 + int skill_times = 1; // 技能使用次数 + battle::SkillUsage alternate_skill_usage = battle::SkillUsage::Possibly; // 备选技能使用模式 + int alternate_skill_times = 1; // 备选技能使用次数 + int auto_retreat = 0; // 部署几秒后自动撤退 +}; + +// 干员组信息,招募相关 +struct RoguelikeGroupInfo +{ + std::string name; // 干员组名 + std::unordered_set opers; // 干员组的所有干员名(用于查重) + int id = -1; // 干员组 id + + // 干员组的所有干员,后续可以基于此重构干员信息存储、作战部署 + std::vector> opers_in_order; // 未初始化,暂不可用 +}; + +class RoguelikeRecruitConfig final : public SingletonHolder, public AbstractConfig +{ +public: + virtual ~RoguelikeRecruitConfig() override = default; + + const RoguelikeOperInfo& get_oper_info(const std::string& theme, const std::string& oper_name) noexcept; + const RoguelikeGroupInfo& get_group_info(const std::string& theme, const std::string& group_name) noexcept; + const std::vector get_team_complete_info(const std::string& theme) const noexcept; + std::vector get_group_ids_of_oper(const std::string& theme, const std::string& oper_name) + const noexcept; // renamed from "get_group_id" + const std::vector get_group_names(const std::string& theme) + const noexcept; // 获取该肉鸽内用到的干员组[干员组1,干员组2, ...], renamed from "get_group_info" + int get_group_id_from_name( + const std::string& theme, + const std::string& group_name) noexcept; // 用干员组名获取干员组 id + const std::string + get_group_name_from_id(const std::string& theme, const int group_id) const noexcept; // 用干员组 id 获取干员组名 + + int get_team_complete_require(const std::string& theme) noexcept { - std::vector groups; // 造成优先级改变的干员组 - int threshold = 0; // 已拥有组内干员数量阈值,达到此阈值时应用优先级改变 - bool is_less = false; // 高于阈值时改变还是低于阈值时改变 - int offset = 0; // 优先级改变的大小 - }; + return m_team_complete_require[theme]; + } // 所有完备度要求的数值总和 - // 收藏品priority影响因子 - struct CollectionPriorityOffset - { - std::string collection; // 造成优先级改变的藏品名称 - int offset = 0; // 优先级改变的大小 - }; - // 干员信息,战斗相关 - struct RoguelikeOperInfo - { - std::string name; - std::vector group_id = {}; // 干员组id,允许一个干员存在于多个干员组 - std::unordered_map order_in_group; // 干员在干员组内的顺序 干员组id:干员组内顺序 - int recruit_priority = 0; // 招募优先级 (0-1000) - int promote_priority = 0; // 晋升优先级 (0-1000) - int recruit_priority_when_team_full = 0; // 队伍满时的招募优先级 (0-1000) - int promote_priority_when_team_full = 0; // 队伍满时的晋升优先级 (0-1000) - std::vector> recruit_priority_offset; // [deprecated] - bool offset_melee = false; // [deprecated] - bool is_key = false; // 是否为核心干员 - bool is_start = false; // 是否为开局干员 - std::vector recruit_priority_offsets; // 招募priority影响因子 - std::vector collection_priority_offsets; // 收藏品priority影响因子 - bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动) - int skill = 0; // 使用几技能 - int alternate_skill = 0; // 当没有指定技能时使用的备选技能,一般是6星干员未精二且精二后使用3技能时才需要指定 - battle::SkillUsage skill_usage = battle::SkillUsage::Possibly; // 技能使用模式 - int skill_times = 1; // 技能使用次数 - battle::SkillUsage alternate_skill_usage = battle::SkillUsage::Possibly; // 备选技能使用模式 - int alternate_skill_times = 1; // 备选技能使用次数 - int auto_retreat = 0; // 部署几秒后自动撤退 - }; +protected: + virtual bool parse(const json::value& json) override; - class RoguelikeRecruitConfig final : public SingletonHolder, public AbstractConfig - { - public: - virtual ~RoguelikeRecruitConfig() override = default; + void clear(const std::string& theme); - const RoguelikeOperInfo& get_oper_info(const std::string& theme, const std::string& name) noexcept; - // 获取该肉鸽内用到的干员组[干员组1,干员组2,...] - const std::vector get_group_info(const std::string& theme) const noexcept; - const std::vector get_team_complete_info(const std::string& theme) const noexcept; - std::vector get_group_id(const std::string& theme, const std::string& name) const noexcept; + std::unordered_map> + m_all_opers; // > + std::unordered_map> + m_all_groups; // > + std::unordered_map> + m_oper_groups; // 保留此变量以保证可能用到的有序性 + std::unordered_map> m_team_complete_condition; // + std::unordered_map m_team_complete_require; // 阵容完备所需干员总数 +}; - protected: - virtual bool parse(const json::value& json) override; - - void clear(const std::string& theme); - - std::unordered_map> m_all_opers; - std::unordered_map> m_oper_groups; - std::unordered_map> m_team_complete_comdition; - }; - - inline static auto& RoguelikeRecruit = RoguelikeRecruitConfig::get_instance(); +inline static auto& RoguelikeRecruit = RoguelikeRecruitConfig::get_instance(); } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp index e586a016dd..734050046d 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp @@ -37,8 +37,7 @@ asst::RoguelikeBattleTaskPlugin::RoguelikeBattleTaskPlugin( bool asst::RoguelikeBattleTaskPlugin::verify(AsstMsg msg, const json::value& details) const { - if (msg != AsstMsg::SubTaskCompleted - || details.get("subtask", std::string()) != "ProcessTask") { + if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") { return false; } @@ -108,8 +107,7 @@ void asst::RoguelikeBattleTaskPlugin::wait_until_start_button_clicked() .run(); } -std::string - asst::RoguelikeBattleTaskPlugin::oper_name_in_config(const battle::DeploymentOper& oper) const +std::string asst::RoguelikeBattleTaskPlugin::oper_name_in_config(const battle::DeploymentOper& oper) const { if (oper.role == Role::Warrior && oper.name == "阿米娅") { return "阿米娅-WARRIOR"; // 在 BattleData.json 中有 @@ -192,7 +190,8 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info() if (opt == std::nullopt) { opt = RoguelikeCopilot.get_stage_data(m_stage_name); } - } else { + } + else { opt = RoguelikeCopilot.get_stage_data(m_stage_name); } @@ -202,10 +201,9 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info() m_blacklist_location = opt->blacklist_location; m_role_order = opt->role_order; 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 }; + 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 }; m_deploy_plan = opt->deploy_plan; m_retreat_plan = opt->retreat_plan; } @@ -224,10 +222,9 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info() } else { auto homes_pos = m_homes | views::transform(&ReplacementHome::location); - auto invalid_homes_pos = homes_pos | views::filter([&](const auto& home_pos) { - return !m_normal_tile_info.contains(home_pos); - }) - | views::transform(&Point::to_string); + auto invalid_homes_pos = + homes_pos | views::filter([&](const auto& home_pos) { return !m_normal_tile_info.contains(home_pos); }) | + views::transform(&Point::to_string); if (!invalid_homes_pos.empty()) { Log.error("No replacement homes point:", invalid_homes_pos); } @@ -249,14 +246,13 @@ bool asst::RoguelikeBattleTaskPlugin::calc_stage_info() return true; } -asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_oper_location_type( - const battle::DeploymentOper& oper) const +asst::battle::LocationType + asst::RoguelikeBattleTaskPlugin::get_oper_location_type(const battle::DeploymentOper& oper) const { return BattleData.get_location_type(oper_name_in_config(oper)); } -asst::battle::OperPosition - asst::RoguelikeBattleTaskPlugin::get_role_position(const battle::Role& role) const +asst::battle::OperPosition asst::RoguelikeBattleTaskPlugin::get_role_position(const battle::Role& role) const { switch (role) { case battle::Role::Support: @@ -270,8 +266,7 @@ asst::battle::OperPosition return battle::OperPosition::Blocking; break; case battle::Role::Medic: - return m_force_air_defense.ban_medic ? battle::OperPosition::None - : battle::OperPosition::AirDefense; + return m_force_air_defense.ban_medic ? battle::OperPosition::None : battle::OperPosition::AirDefense; break; case battle::Role::Special: case battle::Role::Drone: @@ -285,9 +280,7 @@ void asst::RoguelikeBattleTaskPlugin::cache_oper_elite_status() { const auto& oper_list = m_config->get_oper(); for (const auto& oper : m_cur_deployment_opers) { - m_oper_elite.emplace( - oper.name, - oper_list.contains(oper.name) ? oper_list.at(oper.name).elite : 0); + m_oper_elite.emplace(oper.name, oper_list.contains(oper.name) ? oper_list.at(oper.name).elite : 0); } } @@ -318,9 +311,7 @@ void asst::RoguelikeBattleTaskPlugin::set_position_full(const Point& loc, bool f } } -void asst::RoguelikeBattleTaskPlugin::set_position_full( - const battle::DeploymentOper& oper, - bool full) +void asst::RoguelikeBattleTaskPlugin::set_position_full(const battle::DeploymentOper& oper, bool full) { set_position_full(get_oper_location_type(oper), full); } @@ -369,7 +360,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy() // 构造当前地图的部署指令列表 std::vector deploy_plan_list; // 获取当前肉鸽的分组信息[干员组1名称,干员组2名称,...] - const auto& groups = RoguelikeRecruit.get_group_info(m_config->get_theme()); + const auto& groups = RoguelikeRecruit.get_group_names(m_config->get_theme()); // 获取当前肉鸽的分组内排名信息 // const auto& group_rank = RoguelikeRecruit.get_group_rank(rogue_theme); for (const auto& oper : m_cur_deployment_opers) { @@ -383,8 +374,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy() // 获取招募信息 const auto& recruit_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), oper_name); // 获取会用到该干员的干员组[干员组1序号,干员组2序号,...] - std::vector group_ids = - RoguelikeRecruit.get_group_id(m_config->get_theme(), oper_name); + std::vector group_ids = RoguelikeRecruit.get_group_ids_of_oper(m_config->get_theme(), oper_name); for (const auto& group_id : group_ids) { // 当前干员组名,string类型 @@ -393,12 +383,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy() if (m_deploy_plan.contains(group_name)) { for (const auto& info : m_deploy_plan[group_name]) { if (m_kills < info.kill_lower_bound || m_kills > info.kill_upper_bound) { - Log.debug( - " deploy info", - oper.name, - "in group", - group_name, - "is waiting."); + Log.debug(" deploy info", oper.name, "in group", group_name, "is waiting."); is_success = true; // 如果发现了待命干员,此函数最终返回true continue; } @@ -409,9 +394,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy() // deploy_plan.oper_order_in_group = recruit_info.order_in_group.at(group_id); deploy_plan.oper_order_in_group = - recruit_info.order_in_group.empty() - ? INT_MAX - : recruit_info.order_in_group.at(group_id); + recruit_info.order_in_group.empty() ? INT_MAX : recruit_info.order_in_group.at(group_id); deploy_plan.rank = info.rank; deploy_plan.placed = info.location; deploy_plan.direction = info.direction; @@ -434,8 +417,8 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy() } std::sort(deploy_plan_list.begin(), deploy_plan_list.end()); for (const auto& deploy_plan : deploy_plan_list) { - if (!m_used_tiles.contains(deploy_plan.placed) - && !m_blacklist_location.contains(deploy_plan.placed) /* 判断该位置是否已被占据 */) { + if (!m_used_tiles.contains(deploy_plan.placed) && + !m_blacklist_location.contains(deploy_plan.placed) /* 判断该位置是否已被占据 */) { if (auto oper_it = ranges::find_if( m_cur_deployment_opers, [&](const auto& oper) { return oper.name == deploy_plan.oper_name; }); @@ -455,8 +438,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_best_deploy() auto deployed_time = std::chrono::steady_clock::now(); m_deployed_time.insert_or_assign(deploy_plan.oper_name, deployed_time); // 获取技能用法和使用次数 - const auto& oper_info = - RoguelikeRecruit.get_oper_info(m_config->get_theme(), deploy_plan.oper_name); + const auto& oper_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), deploy_plan.oper_name); m_skill_usage[deploy_plan.oper_name] = oper_info.skill_usage; m_skill_times[deploy_plan.oper_name] = oper_info.skill_times; Log.trace(" best deploy is", deploy_plan.oper_name, "with rank", deploy_plan.rank); @@ -476,8 +458,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_once() std::chrono::milliseconds(Config.get_options().roguelike_fight_screencap_interval); // prevent our program from consuming too much CPU - if (const auto now = std::chrono::steady_clock::now(); - prev_frame_time > now - min_frame_interval) [[unlikely]] { + if (const auto now = std::chrono::steady_clock::now(); 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)); } @@ -502,8 +483,8 @@ bool asst::RoguelikeBattleTaskPlugin::do_once() const auto& oper_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), name); auto iter = m_deployed_time.find(name); if (iter != m_deployed_time.end() && oper_info.auto_retreat > 0) { - if (std::chrono::steady_clock::now() - m_deployed_time.at(name) - >= oper_info.auto_retreat * std::chrono::seconds(1)) { + if (std::chrono::steady_clock::now() - m_deployed_time.at(name) >= + oper_info.auto_retreat * std::chrono::seconds(1)) { // 时间到了就撤退 asst::BattleHelper::retreat_oper(name); m_deployed_time.erase(name); @@ -605,8 +586,7 @@ bool asst::RoguelikeBattleTaskPlugin::do_once() auto deployed_time = std::chrono::steady_clock::now(); m_deployed_time.insert_or_assign(best_oper.name, deployed_time); // 获取技能用法和使用次数 - const auto& oper_info = - RoguelikeRecruit.get_oper_info(m_config->get_theme(), best_oper.name); + const auto& oper_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), best_oper.name); m_skill_usage[best_oper.name] = oper_info.skill_usage; m_skill_times[best_oper.name] = oper_info.skill_times; @@ -633,8 +613,7 @@ void asst::RoguelikeBattleTaskPlugin::postproc_of_deployment_conditions( 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()) { + 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); } @@ -659,8 +638,7 @@ void asst::RoguelikeBattleTaskPlugin::postproc_of_deployment_conditions( 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) { + 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"); } @@ -670,8 +648,7 @@ void asst::RoguelikeBattleTaskPlugin::postproc_of_deployment_conditions( break; } - if (m_force_air_defense.has_finished_deploy_air_defense - && position != OperPosition::AirDefense) { + if (m_force_air_defense.has_finished_deploy_air_defense && position != OperPosition::AirDefense) { Log.info("FORCE RANGED OPER DEPLOY END"); m_force_air_defense.has_finished_deploy_air_defense = true; } @@ -684,12 +661,7 @@ void asst::RoguelikeBattleTaskPlugin::check_drone_tiles() while ((!m_need_clear_tiles.empty()) && m_need_clear_tiles.top().placed_time < now_time) { const auto& placed_loc = m_need_clear_tiles.top().placed_loc; 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"); + Log.info("Drone at location (", placed_loc.x, ",", placed_loc.y, ") is recognized as retreated"); set_position_full(placed_loc, false); m_battlefield_opers.erase(iter->second); m_used_tiles.erase(iter); @@ -783,20 +755,12 @@ std::optional asst::RoguelikeBattleTaskPlugin::cal } } - bool use_air_defense = has_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_ranged_full; - bool use_blocking = - has_blocking && m_homes_status[m_cur_home_index].wait_blocking && !m_melee_full; + bool use_air_defense = + has_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_ranged_full; + bool use_blocking = has_blocking && m_homes_status[m_cur_home_index].wait_blocking && !m_melee_full; bool use_medic = has_medic && m_homes_status[m_cur_home_index].wait_medic && !m_ranged_full; - Log.trace( - "use_air_defense", - use_air_defense, - ", use_blocking", - use_blocking, - ", use_medic", - use_medic); + Log.trace("use_air_defense", use_air_defense, ", use_blocking", use_blocking, ", use_medic", use_medic); std::vector cur_available; for (const auto& oper : m_cur_deployment_opers) { @@ -805,9 +769,7 @@ std::optional asst::RoguelikeBattleTaskPlugin::cal } } // 费用高的优先用,放前面 - ranges::sort(cur_available, [](const auto& lhs, const auto& rhs) { - return lhs.cost > rhs.cost; - }); + ranges::sort(cur_available, [](const auto& lhs, const auto& rhs) { return lhs.cost > rhs.cost; }); DeploymentOper best_oper; @@ -889,8 +851,7 @@ void asst::RoguelikeBattleTaskPlugin::clear() m_oper_elite.clear(); } -std::vector - asst::RoguelikeBattleTaskPlugin::available_locations(const DeploymentOper& oper) const +std::vector asst::RoguelikeBattleTaskPlugin::available_locations(const DeploymentOper& oper) const { auto type = get_oper_location_type(oper); if (type == LocationType::Invalid || type == LocationType::None) { @@ -899,18 +860,15 @@ std::vector return available_locations(type); } -std::vector - asst::RoguelikeBattleTaskPlugin::available_locations(battle::LocationType type) const +std::vector asst::RoguelikeBattleTaskPlugin::available_locations(battle::LocationType type) const { std::vector result; for (const auto& [loc, tile] : m_normal_tile_info) { - bool position_matched = - tile.buildable == type || tile.buildable == battle::LocationType::All; - position_matched |= (type == battle::LocationType::All) - && (tile.buildable == battle::LocationType::Melee - || tile.buildable == battle::LocationType::Ranged); - if (position_matched && tile.key != TilePack::TileKey::DeepSea - && // 水上要先放板子才能放人,肉鸽里也没板子,那就当作不可放置 + bool position_matched = tile.buildable == type || tile.buildable == battle::LocationType::All; + position_matched |= (type == battle::LocationType::All) && (tile.buildable == battle::LocationType::Melee || + tile.buildable == battle::LocationType::Ranged); + if (position_matched && + tile.key != TilePack::TileKey::DeepSea && // 水上要先放板子才能放人,肉鸽里也没板子,那就当作不可放置 !m_used_tiles.contains(loc) && !m_blacklist_location.contains(loc)) { result.emplace_back(loc); } @@ -966,10 +924,8 @@ asst::battle::AttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range( } } - for (auto cur_direction : { DeployDirection::Right, - DeployDirection::Up, - DeployDirection::Left, - DeployDirection::Down }) { + for (auto cur_direction : + { DeployDirection::Right, DeployDirection::Up, DeployDirection::Left, DeployDirection::Down }) { if (cur_direction == direction) { break; } @@ -1023,12 +979,10 @@ std::optional // 取距离最近的N个点,计算分数。然后使用得分最高的点 constexpr int CalcPointCount = 4; for (const auto& loc : available_loc | views::take(CalcPointCount)) { - const auto& [cur_direction, cur_socre] = - calc_best_direction_and_score(loc, oper, home.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.location.x) + std::abs(loc.y - home.location.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; @@ -1042,8 +996,7 @@ std::optional } // 强制变化为确定的攻击方向 - if (auto iter = m_force_deploy_direction.find(best_location); - iter != m_force_deploy_direction.end()) { + 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 = iter->second.direction; } @@ -1052,11 +1005,10 @@ std::optional return DeployInfo { best_location, best_direction }; } -asst::RoguelikeBattleTaskPlugin::DirectionAndScore - asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score( - Point loc, - const battle::DeploymentOper& oper, - DeployDirection recommended_direction) const +asst::RoguelikeBattleTaskPlugin::DirectionAndScore asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score( + Point loc, + const battle::DeploymentOper& oper, + DeployDirection recommended_direction) const { LogTraceFunction; @@ -1112,10 +1064,8 @@ asst::RoguelikeBattleTaskPlugin::DirectionAndScore int max_score = 0; DeployDirection best_direction = DeployDirection::None; - for (auto direction : { DeployDirection::Right, - DeployDirection::Up, - DeployDirection::Left, - DeployDirection::Down }) { + for (auto direction : + { DeployDirection::Right, DeployDirection::Up, DeployDirection::Left, DeployDirection::Down }) { int score = 0; // 按朝右算,后面根据方向做转换 for (const Point& relative_pos : get_attack_range(oper, direction)) { @@ -1124,14 +1074,12 @@ asst::RoguelikeBattleTaskPlugin::DirectionAndScore using TileKey = TilePack::TileKey; // 战斗干员朝向的权重 static const std::unordered_map TileKeyFightWeights = { - { TileKey::Invalid, 0 }, { TileKey::Forbidden, 0 }, - { TileKey::Wall, 500 }, { TileKey::Road, 1000 }, - { TileKey::Home, 500 }, { TileKey::EnemyHome, 1000 }, - { TileKey::Airport, 1000 }, { TileKey::Floor, 1000 }, - { TileKey::Hole, 0 }, { TileKey::Telin, 700 }, - { TileKey::Telout, 800 }, { TileKey::Grass, 500 }, - { TileKey::DeepSea, 1000 }, { TileKey::Volcano, 1000 }, - { TileKey::Healing, 1000 }, { TileKey::Fence, 800 }, + { TileKey::Invalid, 0 }, { TileKey::Forbidden, 0 }, { TileKey::Wall, 500 }, + { TileKey::Road, 1000 }, { TileKey::Home, 500 }, { TileKey::EnemyHome, 1000 }, + { TileKey::Airport, 1000 }, { TileKey::Floor, 1000 }, { TileKey::Hole, 0 }, + { TileKey::Telin, 700 }, { TileKey::Telout, 800 }, { TileKey::Grass, 500 }, + { TileKey::DeepSea, 1000 }, { TileKey::Volcano, 1000 }, { TileKey::Healing, 1000 }, + { TileKey::Fence, 800 }, }; // 治疗干员朝向的权重 static const std::unordered_map TileKeyMedicWeights = { @@ -1146,19 +1094,16 @@ asst::RoguelikeBattleTaskPlugin::DirectionAndScore switch (oper.role) { 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) { // 根据哪个方向上人多决定朝向哪 + iter != m_used_tiles.cend() && + BattleData.get_role(iter->second) != battle::Role::Drone) { // 根据哪个方向上人多决定朝向哪 score += 10000; } - if (auto iter = m_side_tile_info.find(absolute_pos); - iter != m_side_tile_info.end()) { + if (auto iter = m_side_tile_info.find(absolute_pos); iter != m_side_tile_info.end()) { score += TileKeyMedicWeights.at(iter->second.key); } break; default: - if (auto iter = m_side_tile_info.find(absolute_pos); - iter != m_side_tile_info.end()) { + if (auto iter = m_side_tile_info.find(absolute_pos); iter != m_side_tile_info.end()) { score += TileKeyFightWeights.at(iter->second.key); } break; diff --git a/src/MaaCore/Task/Roguelike/RoguelikeFormationTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeFormationTaskPlugin.cpp index 5c6838013e..38de372759 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeFormationTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeFormationTaskPlugin.cpp @@ -11,8 +11,7 @@ bool asst::RoguelikeFormationTaskPlugin::verify(AsstMsg msg, const json::value& details) const { - if (msg != AsstMsg::SubTaskCompleted - || details.get("subtask", std::string()) != "ProcessTask") { + if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") { return false; } @@ -73,8 +72,7 @@ bool asst::RoguelikeFormationTaskPlugin::_run() } auto& new_result = formation_analyzer.get_result(); - size_t new_selected_count = - ranges::count_if(new_result, [](const auto& oper) { return oper.selected; }); + size_t new_selected_count = ranges::count_if(new_result, [](const auto& oper) { return oper.selected; }); // 说明 select_count 计数没生效,即都没点上 if (new_selected_count == pre_selected) { reselect = true; @@ -114,20 +112,16 @@ void asst::RoguelikeFormationTaskPlugin::clear_and_reselect() std::vector sorted_oper_list; std::unordered_set oper_to_select; // 和上面的 vector 一致,用于快速查重 - const auto& team_complete_condition = - RoguelikeRecruit.get_team_complete_info(m_config->get_theme()); - const auto& group_list = RoguelikeRecruit.get_group_info(m_config->get_theme()); + const auto& team_complete_condition = RoguelikeRecruit.get_team_complete_info(m_config->get_theme()); + const auto& group_list = RoguelikeRecruit.get_group_names(m_config->get_theme()); for (const auto& condition : team_complete_condition) { // 优先选择阵容核心干员 int count = 0; const int require = condition.threshold; for (const std::string& group_name : condition.groups) { auto group_filter = views::filter([&](const auto& oper) { - const auto& group_ids = - RoguelikeRecruit.get_group_id(m_config->get_theme(), oper.name); - return ranges::any_of(group_ids, [&](int id) { - return group_list[id] == group_name; - }); + const auto& group_ids = RoguelikeRecruit.get_group_ids_of_oper(m_config->get_theme(), oper.name); + return ranges::any_of(group_ids, [&](int id) { return group_list[id] == group_name; }); }); for (const auto& oper : oper_list | group_filter | views::take(require - count)) { if (!oper_to_select.contains(oper.name)) { @@ -143,12 +137,8 @@ void asst::RoguelikeFormationTaskPlugin::clear_and_reselect() } std::erase_if(oper_list, [&](const auto& oper) { return oper_to_select.contains(oper.name); }); - ranges::stable_partition(oper_list, [](const auto& oper) { - return !oper.name.starts_with("预备干员"); - }); - ranges::move( - oper_list | views::take(13 - sorted_oper_list.size()), - std::back_inserter(sorted_oper_list)); + ranges::stable_partition(oper_list, [](const auto& oper) { return !oper.name.starts_with("预备干员"); }); + ranges::move(oper_list | views::take(13 - sorted_oper_list.size()), std::back_inserter(sorted_oper_list)); for (const auto& oper : sorted_oper_list) { select(oper); @@ -174,9 +164,7 @@ bool asst::RoguelikeFormationTaskPlugin::analyze() auto unique_filter = views::filter([&](const auto& oper) { // TODO: 这里没考虑多个相同预备干员的情况,不过影响应该不大 - return !ranges::any_of(oper_list, [&](const auto& existing_oper) { - return oper.name == existing_oper.name; - }); + return !ranges::any_of(oper_list, [&](const auto& existing_oper) { return oper.name == existing_oper.name; }); }); auto append_page_proj = views::transform([&](auto oper) { Log.info(__FUNCTION__, "oper: ", oper.name, " page: ", cur_page); diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp index 56f21a239c..f209a03757 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp @@ -1,7 +1,6 @@ #include "RoguelikeRecruitTaskPlugin.h" #include "Config/Miscellaneous/BattleDataConfig.h" -#include "Config/Roguelike/RoguelikeRecruitConfig.h" #include "Config/TaskData.h" #include "Controller/Controller.h" #include "Status.h" @@ -47,11 +46,26 @@ asst::battle::Role asst::RoguelikeRecruitTaskPlugin::get_oper_role(const std::st bool asst::RoguelikeRecruitTaskPlugin::is_oper_melee(const std::string& name) { const auto role = get_oper_role(name); - if (role != battle::Role::Pioneer && role != battle::Role::Tank && role != battle::Role::Warrior) return false; + if (role != battle::Role::Pioneer && role != battle::Role::Tank && role != battle::Role::Warrior) { + return false; + } const auto loc = BattleData.get_location_type(name); return loc == battle::LocationType::Melee; } +std::unordered_set asst::RoguelikeRecruitTaskPlugin::calculate_condition_oper( + const RecruitPriorityOffset& condition, + const std::unordered_map& chars_map) +{ + std::unordered_set opers; // 符合这个策略组的干员 + for (const auto& [oper_name, oper_level] : chars_map) { + if (condition.opers.contains(oper_name)) { + opers.insert(oper_name); + } + } + return opers; +} + bool asst::RoguelikeRecruitTaskPlugin::_run() { LogTraceFunction; @@ -88,50 +102,48 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() std::unordered_map team_roles; int offset_melee_num = 0; for (const auto& [name, oper] : chars_map) { - if (name.starts_with("预备干员")) continue; + if (name.starts_with("预备干员")) { + continue; + } team_roles[battle::get_role_type(name)]++; - if (is_oper_melee(name)) offset_melee_num++; - } - // __________________will-be-removed-end__________________ - - std::unordered_map group_count; - const auto& group_list = RoguelikeRecruit.get_group_info(m_config->get_theme()); - for (const auto& [name, oper] : chars_map) { - std::vector group_ids = RoguelikeRecruit.get_group_id(m_config->get_theme(), name); - for (const auto& group_id : group_ids) { - const std::string& group_name = group_list[group_id]; - group_count[group_name]++; + if (is_oper_melee(name)) { + offset_melee_num++; } } + // __________________will-be-removed-end__________________ if (!m_starts_complete) { for (const auto& oper : chars_map) { auto& recruit_info = RoguelikeRecruit.get_oper_info(m_config->get_theme(), oper.first); - if (recruit_info.is_start) m_starts_complete = true; + if (recruit_info.is_start) { + m_starts_complete = true; + } } } if (!m_team_complete) { bool complete = true; int complete_count = 0; - int complete_require = 0; const auto& team_complete_condition = RoguelikeRecruit.get_team_complete_info(m_config->get_theme()); - for (const auto& condition : team_complete_condition) { - int count = 0; - complete_require += condition.threshold; - for (const std::string& group_name : condition.groups) { - count += group_count[group_name]; - complete_count += group_count[group_name]; - } - if (count < condition.threshold) { + for (const auto& condition : team_complete_condition) { // 每个完备度的策略组 + std::unordered_set opers = + calculate_condition_oper(condition, chars_map); // 符合这个策略组的干员 + + complete_count += int(opers.size()); + if (int(opers.size()) < condition.threshold) { complete = false; } + + Log.trace("__FUNCTION__", "groups:", condition.groups); + Log.trace("__FUNCTION__", "opers:", opers); } m_team_complete = complete; - if (complete_count <= complete_require / 2 && m_recruit_count >= 10) { + if (complete_count <= RoguelikeRecruit.get_team_complete_require(m_config->get_theme()) / 2 && + m_recruit_count >= 10) { // 如果第10次招募还没拿到半队key干员,说明账号阵容不齐,放开招募限制,有啥用啥吧 - m_team_complete = complete; + m_team_complete = true; } + Log.trace("__FUNCTION__", "complete_count:", complete_count, "m_team_complete:", m_team_complete); } if (m_recruit_count >= 3 && !m_starts_complete) { @@ -180,7 +192,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() std::unordered_set oper_names; const auto& oper_list = analyzer.get_result(); bool stop_swipe = false; - std::vector owned_collection=m_config->get_collection(); + std::vector owned_collection = m_config->get_collection(); int max_oper_x = 0; for (const auto& oper_info : oper_list) { @@ -199,9 +211,17 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() // 偏移低于阈值,代表划动没有效果,已经到达屏幕最右侧 if (x_offset < 20 && y_offset < 5) { stop_swipe = true; - Log.trace(__FUNCTION__, "| Page", i, "oper", oper_info.name, - "last rect:", rect_it->second.to_string(), "current rect:", oper_info.rect.to_string(), - " - stop swiping"); + Log.trace( + __FUNCTION__, + "| Page", + i, + "oper", + oper_info.name, + "last rect:", + rect_it->second.to_string(), + "current rect:", + oper_info.rect.to_string(), + " - stop swiping"); break; } @@ -248,8 +268,12 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() } else { // 精一55级以下,默认不招募 - Log.trace(__FUNCTION__, "| Ignored low level oper:", oper_info.name, oper_info.elite, - oper_info.level); + Log.trace( + __FUNCTION__, + "| Ignored low level oper:", + oper_info.name, + oper_info.elite, + oper_info.level); } if (temp_recruit_exist) { // 临时招募干员具有极高抓取优先级,但是在编队时会占用前面的位置 @@ -277,29 +301,35 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() // } // __________________will-be-removed-end__________________ for (const auto& priority_offset : recruit_info.recruit_priority_offsets) { - int count = 0; - for (const std::string& group_name : priority_offset.groups) { - count += group_count[group_name]; - } + std::unordered_set opers = // 符合这个策略组的干员 + calculate_condition_oper(priority_offset, chars_map); if (priority_offset.is_less) { - if (count <= priority_offset.threshold) priority += priority_offset.offset; + if (int(opers.size()) <= priority_offset.threshold) { + priority += priority_offset.offset; + } } else { - if (count >= priority_offset.threshold) priority += priority_offset.offset; + if (int(opers.size()) >= priority_offset.threshold) { + priority += priority_offset.offset; + } } } - for (const auto& priority_offset : recruit_info.collection_priority_offsets) { + for (const auto& priority_offset : recruit_info.collection_priority_offsets) { auto iter = std::find(owned_collection.begin(), owned_collection.end(), priority_offset.collection); - if (iter != owned_collection.end()) { - priority += priority_offset.offset; - } + if (iter != owned_collection.end()) { + priority += priority_offset.offset; + } } if (!m_starts_complete) { - if (!recruit_info.is_start) priority -= 1000; + if (!recruit_info.is_start) { + priority -= 1000; + } } else if (!m_team_complete) { - if (!recruit_info.is_key) priority -= 1000; + if (!recruit_info.is_key) { + priority -= 1000; + } } } } @@ -310,8 +340,9 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() } // 添加到候选名单 - auto existing_it = ranges::find_if( - recruit_list, [&](const RoguelikeRecruitInfo& pri) -> bool { return pri.name == recruit_info.name; }); + auto existing_it = ranges::find_if(recruit_list, [&](const RoguelikeRecruitInfo& pri) -> bool { + return pri.name == recruit_info.name; + }); if (existing_it == recruit_list.cend()) { RoguelikeRecruitInfo info; info.name = recruit_info.name; @@ -376,7 +407,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() if (info.elite != 2) { continue; } - Log.trace(__FUNCTION__, "| Choose random elite 2:", info.name, info.elite, info.level); + Log.trace(__FUNCTION__, "| Choose temporary recruitment elite 2:", info.name, info.elite, info.level); recruit_oper(info); return true; } @@ -403,8 +434,15 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() } std::string char_name = selected_oper->name; - Log.trace(__FUNCTION__, "| Top priority oper:", char_name, selected_oper->priority, "page", - selected_oper->page_index, "/", i); + Log.trace( + __FUNCTION__, + "| Top priority oper:", + char_name, + selected_oper->priority, + "page", + selected_oper->page_index, + "/", + i); // 滑动方向 // 页码大于一半: 从右往左划动 @@ -426,8 +464,8 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() void asst::RoguelikeRecruitTaskPlugin::reset_in_run_variables() { m_recruit_count = 0; - m_starts_complete = false; - m_team_complete = false; + m_starts_complete = false; + m_team_complete = false; } bool asst::RoguelikeRecruitTaskPlugin::lazy_recruit() @@ -438,7 +476,7 @@ bool asst::RoguelikeRecruitTaskPlugin::lazy_recruit() return true; } - bool asst::RoguelikeRecruitTaskPlugin::recruit_appointed_char(const std::string& char_name, bool is_rtl) +bool asst::RoguelikeRecruitTaskPlugin::recruit_appointed_char(const std::string& char_name, bool is_rtl) { LogTraceFunction; // 最大滑动次数 @@ -464,12 +502,15 @@ bool asst::RoguelikeRecruitTaskPlugin::lazy_recruit() if (analyzer.analyze()) { const auto& chars = analyzer.get_result(); max_oper_x = ranges::max(chars | views::transform([&](const auto& x) { return x.rect.x; })); - auto it = ranges::find_if( - chars, [&](const battle::roguelike::Recruitment& oper) -> bool { return oper.name == char_name; }); + auto it = ranges::find_if(chars, [&](const battle::roguelike::Recruitment& oper) -> bool { + return oper.name == char_name; + }); std::unordered_set oper_names; - ranges::transform(chars, std::inserter(oper_names, oper_names.end()), - std::mem_fn(&battle::roguelike::Recruitment::name)); + ranges::transform( + chars, + std::inserter(oper_names, oper_names.end()), + std::mem_fn(&battle::roguelike::Recruitment::name)); Log.info(__FUNCTION__, "| Oper list:", oper_names); if (it != chars.cend()) { @@ -528,7 +569,9 @@ bool asst::RoguelikeRecruitTaskPlugin::recruit_support_char() auto core_opt = m_config->get_core_char(); m_config->set_core_char(""); if (!core_opt.empty()) { - if (recruit_support_char(core_opt, MaxRefreshTimes)) return true; + if (recruit_support_char(core_opt, MaxRefreshTimes)) { + return true; + } } return false; } @@ -566,17 +609,28 @@ bool asst::RoguelikeRecruitTaskPlugin::recruit_support_char(const std::string& n auto check_satisfy = [&use_nonfriend_support](const RecruitSupportCharInfo& chara) { return chara.is_friend || use_nonfriend_support; }; - std::copy_if(chars_page.begin(), chars_page.end(), - std::inserter(satisfied_chars, std::begin(satisfied_chars)), check_satisfy); + std::copy_if( + chars_page.begin(), + chars_page.end(), + std::inserter(satisfied_chars, std::begin(satisfied_chars)), + check_satisfy); - if (satisfied_chars.size()) break; + if (satisfied_chars.size()) { + break; + } + } + if (page != MaxPageCnt - 1) { + ProcessTask(*this, { "RoguelikeSupportSwipeRight" }).run(); } - if (page != MaxPageCnt - 1) ProcessTask(*this, { "RoguelikeSupportSwipeRight" }).run(); } - if (satisfied_chars.size()) break; + if (satisfied_chars.size()) { + break; + } // 刷新助战 - if (retry >= max_refresh) break; + if (retry >= max_refresh) { + break; + } auto screen_refresh = ctrler()->get_image(); RoguelikeRecruitSupportAnalyzer analyzer_refresh(screen_refresh); analyzer_refresh.set_mode(SupportAnalyzeMode::RefreshSupportBtn); @@ -585,7 +639,9 @@ bool asst::RoguelikeRecruitTaskPlugin::recruit_support_char(const std::string& n return false; } auto& refresh_info = analyzer_refresh.get_result_refresh(); - if (refresh_info.in_cooldown) sleep(refresh_info.remain_secs * 1000); + if (refresh_info.in_cooldown) { + sleep(refresh_info.remain_secs * 1000); + } ctrler()->click(refresh_info.rect); sleep(Task.get("RoguelikeRefreshSupportBtnOcr")->post_delay); ProcessTask(*this, { "RoguelikeSupportSwipeLeft" }).run(); @@ -641,20 +697,24 @@ void asst::RoguelikeRecruitTaskPlugin::slowly_swipe(bool to_left, int swipe_dist { std::string swipe_task_name = to_left ? "RoguelikeRecruitOperListSlowlySwipeToTheLeft" : "RoguelikeRecruitOperListSlowlySwipeToTheRight"; - if (!ControlFeat::support(ctrler()->support_features(), - ControlFeat::PRECISE_SWIPE)) { // 不能精准滑动时不使用 swipe_dist 参数 + if (!ControlFeat::support( + ctrler()->support_features(), + ControlFeat::PRECISE_SWIPE)) { // 不能精准滑动时不使用 swipe_dist 参数 ProcessTask(*this, { swipe_task_name }).run(); return; } - if (!to_left) swipe_dist = -swipe_dist; + if (!to_left) { + swipe_dist = -swipe_dist; + } auto swipe_task = Task.get(swipe_task_name); const Rect& StartPoint = swipe_task->specific_rect; - ctrler()->swipe(StartPoint, - { StartPoint.x + swipe_dist - StartPoint.width, StartPoint.y, StartPoint.width, StartPoint.height }, - swipe_task->special_params.empty() ? 0 : swipe_task->special_params.at(0), - (swipe_task->special_params.size() < 2) ? false : swipe_task->special_params.at(1), - (swipe_task->special_params.size() < 3) ? 1 : swipe_task->special_params.at(2), - (swipe_task->special_params.size() < 4) ? 1 : swipe_task->special_params.at(3)); + ctrler()->swipe( + StartPoint, + { StartPoint.x + swipe_dist - StartPoint.width, StartPoint.y, StartPoint.width, StartPoint.height }, + swipe_task->special_params.empty() ? 0 : swipe_task->special_params.at(0), + (swipe_task->special_params.size() < 2) ? false : swipe_task->special_params.at(1), + (swipe_task->special_params.size() < 3) ? 1 : swipe_task->special_params.at(2), + (swipe_task->special_params.size() < 4) ? 1 : swipe_task->special_params.at(3)); sleep(swipe_task->post_delay); } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h index 69acf43ef9..ec4b46a493 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h @@ -1,15 +1,16 @@ #pragma once #include "AbstractRoguelikeTaskPlugin.h" #include "Common/AsstBattleDef.h" +#include "Config/Roguelike/RoguelikeRecruitConfig.h" namespace asst { // 干员招募信息 struct RoguelikeRecruitInfo { - std::string name; // 干员名字 - int priority = 0; // 招募优先级 (0-1000) - int page_index = 0; // 所在页码 (用于判断翻页方向) + std::string name; // 干员名字 + int priority = 0; // 招募优先级 (0-1000) + int page_index = 0; // 所在页码 (用于判断翻页方向) bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动) }; @@ -49,10 +50,13 @@ private: bool recruit_appointed_char(const std::string& char_name, bool is_rtl = false); // 选择干员 void select_oper(const battle::roguelike::Recruitment& oper); + // 找出给定的 Offset 组所有满足条件的干员 + std::unordered_set calculate_condition_oper( + const RecruitPriorityOffset& condition, + const std::unordered_map& chars_map); - int m_recruit_count = 0; // 第几次招募 - bool m_starts_complete = - false; // 开局干员是否已经招募,阵容中必须有开局干员,没有前仅招募start干员或预备干员 - bool m_team_complete = false; // 阵容是否完备,阵容完备前,仅招募key干员或预备干员 + int m_recruit_count = 0; // 第几次招募 + bool m_starts_complete = false; // 开局干员是否已经招募,阵容中必须有开局干员,没有前仅招募start干员或预备干员 + bool m_team_complete = false; // 阵容是否完备,阵容完备前,仅招募key干员或预备干员 }; }