diff --git a/resource/roguelike/JieGarden/map.json b/resource/roguelike/JieGarden/map.json index 490257ff4d..89073e0915 100644 --- a/resource/roguelike/JieGarden/map.json +++ b/resource/roguelike/JieGarden/map.json @@ -1,6 +1,6 @@ { "theme": "JieGarden", - "node": [ + "nodes": [ { "type": "CombatOps", "template": ["JieGarden@Roguelike@MapNodeCombatOps.png", "JieGarden@Roguelike@MapNodeCombatOpsGrey.png"] diff --git a/resource/roguelike/Sarkaz/map.json b/resource/roguelike/Sarkaz/map.json index 83c1c963de..1bf999852e 100644 --- a/resource/roguelike/Sarkaz/map.json +++ b/resource/roguelike/Sarkaz/map.json @@ -1,6 +1,6 @@ { "theme": "Sarkaz", - "node": [ + "nodes": [ { "type": "CombatOps", "template": ["Sarkaz@Roguelike@MapNodeCombatOps.png", "Sarkaz@Roguelike@MapNodeCombatOpsGrey.png"] diff --git a/resource/tasks/Roguelike/JieGarden.json b/resource/tasks/Roguelike/JieGarden.json index 5ca6231845..eaf891718a 100644 --- a/resource/tasks/Roguelike/JieGarden.json +++ b/resource/tasks/Roguelike/JieGarden.json @@ -496,7 +496,7 @@ ], "next": ["JieGarden@Roguelike@RoutingAction"] }, - "JieGarden@Roguelike@Routing-BoskyPassage_JieGarden": { + "JieGarden@Roguelike@Routing_BoskyPassage": { "template": [ "JieGarden@Roguelike@MapNodeOldShop.png", "JieGarden@Roguelike@MapNodeScheme.png", @@ -1120,8 +1120,8 @@ "JieGarden@Roguelike@RandomPickAfterNextLevel", "JieGarden@Roguelike@CloseCollectionContinue", "JieGarden@Roguelike@CloseCollectionClose", - "JieGarden@Roguelike@Routing-BoskyPassage_JieGarden", - "JieGarden@Roguelike@ClickRemainingCandleFlame" + "JieGarden@Roguelike@Routing_BoskyPassage", + "JieGarden@Roguelike@Stages_leaveBoskyPassage#next" ] }, "JieGarden@Roguelike@Stages_default": { diff --git a/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.cpp b/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.cpp index c2c22a69bb..acd2d82652 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.cpp +++ b/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.cpp @@ -4,24 +4,100 @@ #include "Utils/Logger.hpp" -bool asst::RoguelikeMapConfig::parse(const json::value& json) +using namespace asst; + +static const std::unordered_map NODE_TYPE_MAPPING = { + { "CombatOps", RoguelikeNodeType::CombatOps }, + { "EmergencyOps", RoguelikeNodeType::EmergencyOps }, + { "DreadfulFoe", RoguelikeNodeType::DreadfulFoe }, + { "Encounter", RoguelikeNodeType::Encounter }, + { "Boons", RoguelikeNodeType::Boons }, + { "SafeHouse", RoguelikeNodeType::SafeHouse }, + { "Recreation", RoguelikeNodeType::Recreation }, + { "RogueTrader", RoguelikeNodeType::RogueTrader }, + { "RegionalCommissions", RoguelikeNodeType::RegionalCommissions }, + { "LostAndFound", RoguelikeNodeType::LostAndFound }, + { "Scout", RoguelikeNodeType::Scout }, + { "BoskyPassage", RoguelikeNodeType::BoskyPassage }, + { "Prophecy", RoguelikeNodeType::Prophecy }, + { "MysteriousPresage", RoguelikeNodeType::MysteriousPresage }, + { "FerociousPresage", RoguelikeNodeType::FerociousPresage }, + { "IdeaFilter", RoguelikeNodeType::IdeaFilter }, + { "FaceOff", RoguelikeNodeType::FaceOff }, + { "Legend", RoguelikeNodeType::Legend }, + { "Omissions", RoguelikeNodeType::Omissions }, + { "Doubts", RoguelikeNodeType::Doubts }, + { "Disaster", RoguelikeNodeType::Disaster }, + { "Playtime", RoguelikeNodeType::Playtime }, + { "OldShop", RoguelikeNodeType::OldShop }, + { "YiTrader", RoguelikeNodeType::YiTrader }, + { "Scheme", RoguelikeNodeType::Scheme } +}; + +bool RoguelikeMapConfig::parse(const json::value& json) { LogTraceFunction; const std::string theme = json.at("theme").as_string(); m_templ_type_mappings.erase(theme); - for (const auto& node_json : json.at("node").as_array()) { - const RoguelikeNodeType node_type = name2type(node_json.at("type").as_string()); + for (const auto& node_json : json.at("nodes").as_array()) { + const std::string& type_name = node_json.at("type").as_string(); + auto it = NODE_TYPE_MAPPING.find(type_name); + if (it == NODE_TYPE_MAPPING.end()) { + Log.error("RoguelikeMapConfig::parse | Unknown roguelike node type name:", type_name); + return false; + } + RoguelikeNodeType type = it->second; for (const auto& template_json : node_json.at("template").as_array()) { const std::string node_template = template_json.as_string(); - m_templ_type_mappings[theme].emplace(node_template, node_type); + m_templ_type_mappings[theme].emplace(node_template, type); } } return true; } -void asst::RoguelikeMapConfig::clear() +RoguelikeNodeType RoguelikeMapConfig::templ2type(const std::string& theme, const std::string& templ_name) const { - m_templ_type_mappings.clear(); + auto outer_it = m_templ_type_mappings.find(theme); + if (outer_it == m_templ_type_mappings.end()) { + Log.error(__FUNCTION__, "| Unsupported roguelike theme:", theme); + return RoguelikeNodeType::Unknown; + } + const auto& templ_type_mapping = outer_it->second; + auto inner_it = templ_type_mapping.find(templ_name); + if (inner_it == templ_type_mapping.end()) { + Log.error(__FUNCTION__, "| No roguelike node type is specified for template", templ_name); + return RoguelikeNodeType::Unknown; + } + return inner_it->second; +} + +std::string RoguelikeMapConfig::type2name(RoguelikeNodeType type) +{ + static const std::unordered_map NODE_NAME_MAPPING = []() { + std::unordered_map mapping; + mapping.reserve(NODE_TYPE_MAPPING.size()); + for (const auto& [name_, type_] : NODE_TYPE_MAPPING) { + mapping.emplace(type_, name_); + } + return mapping; + }(); + + auto it = NODE_NAME_MAPPING.find(type); + if (it == NODE_NAME_MAPPING.end()) { + Log.error(__FUNCTION__, "| Unknown roguelike node type", static_cast(type)); + return "Unknown"; + } + return it->second; +} + +RoguelikeNodeType RoguelikeMapConfig::name2type(const std::string& type_name) +{ + auto it = NODE_TYPE_MAPPING.find(type_name); + if (it == NODE_TYPE_MAPPING.end()) { + Log.error(__FUNCTION__, "| Unknown roguelike node type name:", type_name); + return RoguelikeNodeType::Unknown; + } + return it->second; } diff --git a/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.h b/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.h index 503b5db7dd..f8d3fc0419 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.h +++ b/src/MaaCore/Config/Roguelike/RoguelikeMapConfig.h @@ -1,26 +1,56 @@ #pragma once #include "Config/AbstractConfig.h" -#include "Task/Roguelike/Map/RoguelikeMap.h" namespace asst { +enum class RoguelikeNodeType +{ + Init = -1, // Init + Unknown = 0, // 未知 + CombatOps, // 作战 + EmergencyOps, // 紧急作战 + DreadfulFoe, // 险路恶敌 + Encounter, // 不期而遇 + Boons, // 古堡馈赠/得偿所愿 + SafeHouse, // 安全的角落 + Recreation, // 幕间余兴/兴致盎然 + RogueTrader, // 诡意行商 + // –––––––– 水月主题引入 –––––––––––––––––––––––– + RegionalCommissions, // 地区委托 + LostAndFound, // 风雨际会/失与得 + Scout, // 紧急运输/先行一步 + BoskyPassage, // 误入奇境/树篱之途 + // –––––––– 萨米主题引入 –––––––––––––––––––––––– + Prophecy, // 命运所指 + MysteriousPresage, // 诡秘的预感 + FerociousPresage, // 凶戾的预感 + // –––––––– 萨卡兹主题引入 –––––––––––––––––––––––– + IdeaFilter, // 去违存真 + FaceOff, // 狭路相逢 + // –––––––– 界园岁兽残识引入 –––––––––––––––––––––––– + Legend, // 传说 + Omissions, // 拾遗 + Doubts, // 杂疑 + Disaster, // 祸乱 + Playtime, // 常乐 + OldShop, // 故肆 + YiTrader, // 易与 + Scheme, // 筹谋 +}; + class RoguelikeMapConfig final : public SingletonHolder, public AbstractConfig { public: virtual ~RoguelikeMapConfig() override = default; - const RoguelikeNodeType& templ2type(const std::string& theme, const std::string& templ_name) const noexcept - { - const auto& templ_type_mapping = m_templ_type_mappings.at(theme); - return templ_type_mapping.at(templ_name); - } + RoguelikeNodeType templ2type(const std::string& theme, const std::string& templ_name) const; + static std::string type2name(RoguelikeNodeType type); + static RoguelikeNodeType name2type(const std::string& type_name); private: virtual bool parse(const json::value& json) override; - void clear(); - std::unordered_map> m_templ_type_mappings; }; diff --git a/src/MaaCore/Task/Interface/RoguelikeTask.cpp b/src/MaaCore/Task/Interface/RoguelikeTask.cpp index 63f3da8537..94e31de154 100644 --- a/src/MaaCore/Task/Interface/RoguelikeTask.cpp +++ b/src/MaaCore/Task/Interface/RoguelikeTask.cpp @@ -34,9 +34,12 @@ #include "Task/Roguelike/Sami/RoguelikeFoldartalUseTaskPlugin.h" // ------------------ 萨卡兹主题专用配置及插件 ------------------ -#include "Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h" #include "Task/Roguelike/RoguelikeInputSeedTaskPlugin.h" +// ------------------ 导航相关配置及插件 ------------------ +#include "Task/Roguelike/Map/RoguelikeBoskyPassageRoutingTaskPlugin.h" +#include "Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h" + #include "Utils/Logger.hpp" asst::RoguelikeTask::RoguelikeTask(const AsstCallback& callback, Assistant* inst) : @@ -95,9 +98,12 @@ asst::RoguelikeTask::RoguelikeTask(const AsstCallback& callback, Assistant* inst m_roguelike_task_ptr->register_plugin(m_config_ptr, m_control_ptr); // ------------------ 萨卡兹主题专用插件 ------------------ - m_roguelike_task_ptr->register_plugin(m_config_ptr, m_control_ptr); m_roguelike_task_ptr->register_plugin(m_config_ptr, m_control_ptr); + // ------------------ 导航相关插件 ------------------ + m_roguelike_task_ptr->register_plugin(m_config_ptr, m_control_ptr); + m_roguelike_task_ptr->register_plugin(m_config_ptr, m_control_ptr); + m_subtasks.emplace_back(m_roguelike_task_ptr); } diff --git a/src/MaaCore/Task/Roguelike/Map/AbstractRoguelikeMap.cpp b/src/MaaCore/Task/Roguelike/Map/AbstractRoguelikeMap.cpp deleted file mode 100644 index 2dd07e3a62..0000000000 --- a/src/MaaCore/Task/Roguelike/Map/AbstractRoguelikeMap.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "AbstractRoguelikeMap.h" -#include "RoguelikeBoskyPassageMap.h" - -#include - -namespace asst -{ - -// 共用的节点类型映射表 -static const std::unordered_map NodeTypeMapping = { - { "CombatOps", RoguelikeNodeType::CombatOps }, - { "EmergencyOps", RoguelikeNodeType::EmergencyOps }, - { "DreadfulFoe", RoguelikeNodeType::DreadfulFoe }, - { "Encounter", RoguelikeNodeType::Encounter }, - { "Boons", RoguelikeNodeType::Boons }, - { "SafeHouse", RoguelikeNodeType::SafeHouse }, - { "Recreation", RoguelikeNodeType::Recreation }, - { "RogueTrader", RoguelikeNodeType::RogueTrader }, - { "RegionalCommissions", RoguelikeNodeType::RegionalCommissions }, - { "LostAndFound", RoguelikeNodeType::LostAndFound }, - { "Scout", RoguelikeNodeType::Scout }, - { "BoskyPassage", RoguelikeNodeType::BoskyPassage }, - { "Prophecy", RoguelikeNodeType::Prophecy }, - { "MysteriousPresage", RoguelikeNodeType::MysteriousPresage }, - { "FerociousPresage", RoguelikeNodeType::FerociousPresage }, - { "IdeaFilter", RoguelikeNodeType::IdeaFilter }, - { "FaceOff", RoguelikeNodeType::FaceOff }, - { "Legend", RoguelikeNodeType::Legend }, - { "Omissions", RoguelikeNodeType::Omissions }, - { "Doubts", RoguelikeNodeType::Doubts }, - { "Disaster", RoguelikeNodeType::Disaster }, - { "Playtime", RoguelikeNodeType::Playtime }, - { "OldShop", RoguelikeNodeType::OldShop }, - { "YiTrader", RoguelikeNodeType::YiTrader }, - { "Scheme", RoguelikeNodeType::Scheme } -}; - -RoguelikeNodeType name2type(const std::string& node_name) -{ - auto it = NodeTypeMapping.find(node_name); - return it != NodeTypeMapping.end() ? it->second : RoguelikeNodeType::Unknown; -} - -std::string type2name(RoguelikeNodeType node_type) -{ - static const std::unordered_map reverse_mapping = []() { - std::unordered_map mapping; - for (const auto& [name, type] : NodeTypeMapping) { - mapping[type] = name; - } - return mapping; - }(); - - auto it = reverse_mapping.find(node_type); - return it != reverse_mapping.end() ? it->second : "Unknown"; -} - -std::string subtype2name(RoguelikeBoskySubNodeType sub_node_type) -{ - // 界园树洞子节点类型映射表 - static const std::unordered_map subtype_mapping = { - { RoguelikeBoskySubNodeType::Unknown, "Unknown" }, - { RoguelikeBoskySubNodeType::Ling, "Ling" }, // 令 - 常乐 掷地有声 - { RoguelikeBoskySubNodeType::Shu, "Shu" }, // 黍 - 常乐 种因得果 - { RoguelikeBoskySubNodeType::Nian, "Nian" } // 年 - 常乐 三缺一 - }; - - auto it = subtype_mapping.find(sub_node_type); - return it != subtype_mapping.end() ? it->second : "Unknown"; -} - -} // namespace asst diff --git a/src/MaaCore/Task/Roguelike/Map/AbstractRoguelikeMap.h b/src/MaaCore/Task/Roguelike/Map/AbstractRoguelikeMap.h deleted file mode 100644 index 7b3649997b..0000000000 --- a/src/MaaCore/Task/Roguelike/Map/AbstractRoguelikeMap.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include - -namespace asst -{ - -enum class RoguelikeBoskySubNodeType; - -enum class RoguelikeNodeType -{ - Init = -1, // Init - Unknown = 0, // 未知 - CombatOps = 1, // 作战 - EmergencyOps = 2, // 紧急作战 - DreadfulFoe = 3, // 险路恶敌 - Encounter = 4, // 不期而遇 - Boons = 5, // 古堡馈赠/得偿所愿 - SafeHouse = 6, // 安全的角落 - Recreation = 7, // 幕间余兴/兴致盎然 - RogueTrader = 8, // 诡意行商 - // –––––––– 水月主题引入 –––––––––––––––––––––––– - RegionalCommissions = 9, // 地区委托 - LostAndFound = 10, // 风雨际会/失与得 - Scout = 11, // 紧急运输/先行一步 - BoskyPassage = 12, // 误入奇境/树篱之途 - // –––––––– 萨米主题引入 –––––––––––––––––––––––– - Prophecy = 13, // 命运所指 - MysteriousPresage = 14, // 诡秘的预感 - FerociousPresage = 15, // 凶戾的预感 - // –––––––– 萨卡兹主题引入 –––––––––––––––––––––––– - IdeaFilter = 16, // 去违存真 - FaceOff = 17, // 狭路相逢 - // –––––––– 界园树洞引入 ––––––––-––––––––––––––– - Legend = 18, // 传说 - Omissions = 19, // 拾遗 - Doubts = 20, // 杂疑 - Disaster = 21, // 祸乱 - Playtime = 22, // 常乐 - OldShop = 23, // 故肆 - YiTrader = 24, // 易与 - Scheme = 25 // 筹谋 -}; - -// 根据字符串获取节点类型 -RoguelikeNodeType name2type(const std::string& node_name); - -// 获取节点类型的字符串名称 -std::string type2name(RoguelikeNodeType node_type); - -// 获取树篱之途子节点类型的字符串名称 -std::string subtype2name(RoguelikeBoskySubNodeType sub_node_type); - -} // namespace asst diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.cpp b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.cpp index 52c42e4155..ef1f5acd3b 100644 --- a/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.cpp +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.cpp @@ -1,11 +1,27 @@ #include "RoguelikeBoskyPassageMap.h" -#include "AbstractRoguelikeMap.h" -#include "Utils/Logger.hpp" #include +#include "Utils/Logger.hpp" + namespace asst { +std::string subtype2name(RoguelikeBoskySubNodeType node_subtype) +{ + // 界园树洞子节点类型映射表 + static const std::unordered_map NODE_SUBTYPE_MAPPING = { + { RoguelikeBoskySubNodeType::Unknown, "Unknown" }, + { RoguelikeBoskySubNodeType::Ling, "Ling" }, // 令 - 常乐 掷地有声 + { RoguelikeBoskySubNodeType::Shu, "Shu" }, // 黍 - 常乐 种因得果 + { RoguelikeBoskySubNodeType::Nian, "Nian" } // 年 - 常乐 三缺一 + }; + + if (auto it = NODE_SUBTYPE_MAPPING.find(node_subtype); it != NODE_SUBTYPE_MAPPING.end()) { + return it->second; + } + return "Unnamed"; +} + std::optional RoguelikeBoskyPassageMap::create_and_insert_node(int x, int y, RoguelikeNodeType type, bool is_open) { diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.h b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.h index 1ffe1c4540..1c1fc2d6db 100644 --- a/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.h +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageMap.h @@ -1,12 +1,12 @@ #pragma once -#include "AbstractRoguelikeMap.h" -#include "Utils/SingletonHolder.hpp" - #include #include #include +#include "Config/Roguelike/RoguelikeMapConfig.h" +#include "Utils/SingletonHolder.hpp" + namespace asst { @@ -18,6 +18,9 @@ enum class RoguelikeBoskySubNodeType Nian = 3, // 年 - 常乐 三缺一 }; +// 获取树篱之途子节点类型的字符串名称 +std::string subtype2name(RoguelikeBoskySubNodeType sub_node_type); + class RoguelikeBoskyPassageMap : public SingletonHolder { public: diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageRoutingTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageRoutingTaskPlugin.cpp new file mode 100644 index 0000000000..5beae67e31 --- /dev/null +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageRoutingTaskPlugin.cpp @@ -0,0 +1,294 @@ +#include "RoguelikeBoskyPassageRoutingTaskPlugin.h" + +#include "Config/TaskData.h" +#include "Controller/Controller.h" +#include "Utils/ImageIo.hpp" +#include "Utils/Logger.hpp" +#include "Utils/NoWarningCV.h" +#include "Vision/MultiMatcher.h" + +bool asst::RoguelikeBoskyPassageRoutingTaskPlugin::load_params([[maybe_unused]] const json::value& params) +{ + if (m_config->get_theme() == RoguelikeTheme::JieGarden) { + // ———————— 加载 BoskyPassage 配置 ———————— + const std::shared_ptr bosky_config = + Task.get("JieGarden@Roguelike@RoutingBoskyPassageConfig"); + m_bosky_config = bosky_config->special_params; + + // ———————— 选择导航策略 ———————— + if (m_config->get_mode() == RoguelikeMode::FindPlaytime) { + m_bosky_routing_strategy = RoutingStrategy::FindPlaytime_JieGarden; + int target = m_config->get_find_playTime_target(); + RoguelikeBoskyPassageMap::get_instance().set_target_subtype(static_cast(target)); + Log.info(__FUNCTION__, "| FindPlaytime mode enabled with target:", target); + return true; + } + + m_bosky_routing_strategy = RoutingStrategy::BoskyPassage_JieGarden; + return true; + } + + return false; +} + +void asst::RoguelikeBoskyPassageRoutingTaskPlugin::reset_in_run_variables() +{ + RoguelikeBoskyPassageMap::get_instance().reset(); +} + +bool asst::RoguelikeBoskyPassageRoutingTaskPlugin::verify(const AsstMsg msg, const json::value& details) const +{ + if (msg != AsstMsg::SubTaskStart || details.get("subtask", std::string()) != "ProcessTask") { + return false; + } + + std::string task_name = details.get("details", "task", ""); + // Log.debug(__FUNCTION__, "| Checking task:", task_name); // 不太建议加这个,会在日志中大量出现 + + // trigger 任务的名字可以为 "...@Roguelike@Routing_BoskyPassage-..." 的形式 + if (const size_t pos = task_name.find('-'); pos != std::string::npos) { + task_name = task_name.substr(0, pos); + } + + if (task_name == m_config->get_theme() + "@Roguelike@Routing_BoskyPassage") { + return true; + } + + return false; +} + +bool asst::RoguelikeBoskyPassageRoutingTaskPlugin::_run() +{ + LogTraceFunction; + + Log.info(__FUNCTION__, "| Running with bosky_routing_strategy:", static_cast(m_bosky_routing_strategy)); + + switch (m_bosky_routing_strategy) { + case RoutingStrategy::BoskyPassage_JieGarden: { + bosky_update_map(); + const std::vector priority_order = get_bosky_passage_priority("Default"); + bosky_decide_and_click(priority_order); + break; + } + case RoutingStrategy::FindPlaytime_JieGarden: { + // 更新地图 + bosky_update_map(); + const std::vector priority_order = get_bosky_passage_priority("FindPlaytime"); + + // 获取目标常乐节点子类型 + Log.info( + __FUNCTION__, + "| Looking for playtime subtype:", + subtype2name(RoguelikeBoskyPassageMap::get_instance().get_target_subtype())); + + // 尝试找到目标节点,使用常乐节点优先的策略 + bosky_decide_and_click(priority_order); + break; + } + default: + break; + } + + return true; +} + +// ==================== JieGarden BoskyPassage 平面地图逻辑 ==================== +void asst::RoguelikeBoskyPassageRoutingTaskPlugin::bosky_update_map() +{ + LogTraceFunction; + + Log.info(__FUNCTION__, "| updating bosky map"); + + cv::Mat image = ctrler()->get_image(); + if (image.empty()) { + Log.error(__FUNCTION__, "| Failed to get image from controller"); + return; + } + + MultiMatcher node_analyzer(image); + node_analyzer.set_task_info("JieGarden@Roguelike@RoutingBoskyPassageNodeAnalyze"); + if (!node_analyzer.analyze()) { + Log.error(__FUNCTION__, "| no nodes are recognised"); + return; + } + + MultiMatcher::ResultsVec match_results = node_analyzer.get_result(); + Log.info(__FUNCTION__, "| found", match_results.size(), "nodes"); + + // 排序 靠左上优先 + sort_by_vertical_(match_results); + + const std::string& theme = m_config->get_theme(); + +#ifdef ASST_DEBUG + cv::Mat image_draw = image.clone(); +#endif + + // 处理每个识别到的节点 + for (const auto& [rect, score, templ_name] : match_results) { + Log.debug(__FUNCTION__, "| analyzing node", templ_name, "at (", rect.x, ",", rect.y, ")"); + + const RoguelikeNodeType type = RoguelikeMapInfo.templ2type(theme, templ_name); + if (type == RoguelikeNodeType::Unknown) { + Log.warn(__FUNCTION__, "| unknown template:", templ_name); + continue; + } + + // 检查是否为灰色节点 + const bool is_open = templ_name.find("Grey") == std::string::npos; + + auto idx = RoguelikeBoskyPassageMap::get_instance() + .ensure_node_from_pixel(rect.x, rect.y, m_bosky_config, is_open, type); + + if (idx.has_value()) { + // 更新节点类型(防止类型不一致) + RoguelikeBoskyPassageMap::get_instance().set_node_type(idx.value(), type); + Log.debug(__FUNCTION__, "| updated node (", idx.value(), ") type: (", type2name(type), ")"); + } + else { + Log.warn(__FUNCTION__, "| failed to create/update node from pixel (", rect.x, ",", rect.y, ")"); + } + +#ifdef ASST_DEBUG + if (idx.has_value()) { + cv::rectangle(image_draw, make_rect(rect), cv::Scalar(0, 0, 255), 2); + cv::putText( + image_draw, + std::to_string(static_cast(type)) + " (" + + std::to_string(RoguelikeBoskyPassageMap::get_instance().get_node_x(idx.value())) + ", " + + std::to_string(RoguelikeBoskyPassageMap::get_instance().get_node_y(idx.value())) + ")", + cv::Point(rect.x, rect.y - 5), + cv::FONT_HERSHEY_SIMPLEX, + 0.5, + cv::Scalar(0, 0, 255), + 1); + } +#endif + } + +#ifdef ASST_DEBUG + const std::filesystem::path& relative_dir = utils::path("debug") / utils::path("roguelikeMap"); + const auto relative_path = relative_dir / (std::format("{}_bosky_draw.png", utils::format_now_for_filename())); + Log.debug(__FUNCTION__, "| Saving bosky map image to", relative_path); + asst::imwrite(relative_path, image_draw); +#endif + + Log.info(__FUNCTION__, "| map updated with", RoguelikeBoskyPassageMap::get_instance().size(), "nodes"); +} + +void asst::RoguelikeBoskyPassageRoutingTaskPlugin::bosky_decide_and_click( + const std::vector& priority_order) +{ + LogTraceFunction; + + Log.info(__FUNCTION__, "| deciding and clicking a bosky passage node"); + + size_t chosen = 0; + bool found = false; + + // 按优先级顺序查找可用的节点 + for (const auto& node_type : priority_order) { + auto nodes_of_type = RoguelikeBoskyPassageMap::get_instance().get_open_unvisited_nodes(node_type); + if (!nodes_of_type.empty()) { + chosen = nodes_of_type.front(); + found = true; + Log.debug(__FUNCTION__, "| found node of type (", type2name(node_type), ") with index (", chosen, ")"); + break; + } + } + + if (!found) { + Log.info(__FUNCTION__, "| no open unvisited nodes available"); + Task.set_task_base( + "JieGarden@Roguelike@RoutingAction", + "JieGarden@Roguelike@RoutingAction-ClickRemainingCandleFlame"); + return; + } + int gx = RoguelikeBoskyPassageMap::get_instance().get_node_x(chosen); + int gy = RoguelikeBoskyPassageMap::get_instance().get_node_y(chosen); + RoguelikeNodeType node_type = RoguelikeBoskyPassageMap::get_instance().get_node_type(chosen); + + Log.info(__FUNCTION__, "| chosen node:", chosen, "(", gx, ",", gy, ") type:", type2name(node_type)); + + // 点击节点中心 + auto [px, py] = RoguelikeBoskyPassageMap::get_instance().get_node_pixel( + chosen, + m_bosky_config.origin_x, + m_bosky_config.origin_y, + m_bosky_config.column_offset, + m_bosky_config.row_offset); + + if (px == -1 || py == -1) { + Log.error(__FUNCTION__, "| Invalid pixel coordinates for node", chosen, ": (", px, ",", py, ")"); + return; + } + + Point click_point(px + m_bosky_config.node_width / 2, py + m_bosky_config.node_height / 2); + sleep(200); + ctrler()->click(click_point); + RoguelikeBoskyPassageMap::get_instance().set_visited(chosen); + RoguelikeBoskyPassageMap::get_instance().set_curr_pos(chosen); + + // 发送节点类型到 WPF + std::string node_type_name = type2name(node_type); + auto node_info = basic_info_with_what("BoskyPassageNode"); + node_info["details"]["node_type"] = node_type_name; + callback(AsstMsg::SubTaskExtraInfo, node_info); + + // 执行节点类型对应的任务 + const std::string& theme = m_config->get_theme(); + std::string node_name = type2name(node_type); + + const std::string node_task_name = theme + "@Roguelike@MapNode" + node_name; + // 设置 next + Task.set_task_base("JieGarden@Roguelike@RoutingAction", node_task_name); +} + +std::vector + asst::RoguelikeBoskyPassageRoutingTaskPlugin::get_bosky_passage_priority(const std::string& strategy) +{ + LogTraceFunction; + + const std::string& theme = m_config->get_theme(); + const std::string config_name = theme + "@Roguelike@RoutingBoskyPassagePriority_" + strategy; + + auto task_info = Task.get(config_name); + if (!task_info) { + Log.error(__FUNCTION__, "| priority config not found:", config_name); + return {}; + } + + // 从 next 字段中读取优先级配置 + const auto& next_tasks = task_info->next; + if (next_tasks.empty()) { + Log.warn(__FUNCTION__, "| Priority config is empty in:", config_name); + return {}; + } + + // 从任务名称中解析节点类型 + std::vector priority_order; + priority_order.reserve(next_tasks.size()); + + for (const std::string& task_name : next_tasks) { + // 解析类似 "JieGarden@Roguelike@MapNodeYiTrader" 这样的任务名 -> "YiTrader" + constexpr std::string_view map_node_prefix = "MapNode"; + const size_t pos = task_name.rfind(map_node_prefix); + if (pos == std::string::npos) { + Log.warn(__FUNCTION__, "| Invalid task name in priority config:", task_name); + continue; + } + + const std::string node_name = task_name.substr(pos + map_node_prefix.length()); + RoguelikeNodeType node_type = name2type(node_name); + if (node_type != RoguelikeNodeType::Unknown) { + priority_order.push_back(node_type); + Log.debug(__FUNCTION__, "| Added priority node type:", type2name(node_type), "from task:", task_name); + } + else { + Log.warn(__FUNCTION__, "| Failed to parse node type from task:", task_name); + } + } + + Log.info(__FUNCTION__, "| Loaded", priority_order.size(), "node types from priority config"); + return priority_order; +} diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageRoutingTaskPlugin.h b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageRoutingTaskPlugin.h new file mode 100644 index 0000000000..4b7fc1ae1a --- /dev/null +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeBoskyPassageRoutingTaskPlugin.h @@ -0,0 +1,40 @@ +#pragma once + +#include "RoguelikeBoskyPassageMap.h" +#include "Task/Roguelike/AbstractRoguelikeTaskPlugin.h" + +namespace asst +{ +class RoguelikeBoskyPassageRoutingTaskPlugin : public AbstractRoguelikeTaskPlugin +{ +public: + using AbstractRoguelikeTaskPlugin::AbstractRoguelikeTaskPlugin; + virtual ~RoguelikeBoskyPassageRoutingTaskPlugin() override = default; + virtual bool verify(AsstMsg msg, const json::value& details) const override; + virtual bool load_params(const json::value& params) override; + virtual void reset_in_run_variables() override; + + enum class RoutingStrategy + { + None, + BoskyPassage_JieGarden, + FindPlaytime_JieGarden, + }; + +protected: + virtual bool _run() override; + +private: + void bosky_update_map(); // 从当前截图识别所有可见节点并更新/创建 + void bosky_decide_and_click(const std::vector& priority_order); // 策略,可指定优先级策略 + std::vector get_bosky_passage_priority(const std::string& strategy); // 从配置文件读取优先级 + + inline static std::function type2name = &RoguelikeMapConfig::type2name; + inline static std::function name2type = &RoguelikeMapConfig::name2type; + // ———————— constants and variables ——————————————————————————————————————————————— + RoutingStrategy m_bosky_routing_strategy = RoutingStrategy::None; + RoguelikeBoskyPassageMap::BoskyPassageMapConfig m_bosky_config; + + // 界园树洞地图使用单例模式,通过 RoguelikeBoskyPassageMap::get_instance() 访问 +}; +} diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.cpp b/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.cpp index decc6ca4a6..85fe3c9dde 100644 --- a/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.cpp +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.cpp @@ -16,7 +16,7 @@ asst::RoguelikeMap::RoguelikeMap() // ———————————————————————————————————————————————————————————————————————————————— std::optional - asst::RoguelikeMap::create_and_insert_node(const RoguelikeNodeType& type, const size_t& column, const int& y) + asst::RoguelikeMap::create_and_insert_node(const RoguelikeNodeType type, const size_t& column, const int& y) { const RoguelikeNodePtr node = std::make_shared(type, column, y); return insert_node(node, column); diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.h b/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.h index 34bbcc738a..f7c7b9c8e2 100644 --- a/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.h +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeMap.h @@ -1,12 +1,12 @@ #pragma once -#include "AbstractRoguelikeMap.h" - #include #include #include #include +#include "Config/Roguelike/RoguelikeMapConfig.h" + namespace asst { struct RoguelikeNode @@ -21,10 +21,10 @@ struct RoguelikeNode // –––––––– 萨卡兹主题引入 –––––––––––––––––––––––– int refresh_times = 0; - RoguelikeNode(const RoguelikeNodeType _t, const size_t _c, const int _y) : - type(_t), - column(_c), - y(_y) + RoguelikeNode(const RoguelikeNodeType type_, const size_t column_, const int y_) : + type(type_), + column(column_), + y(y_) { } }; @@ -45,7 +45,7 @@ public: // ———————— update map ———————————————————————————————————————————————————————————— // 创建并插入一个 node,返回新 node 的 index - std::optional create_and_insert_node(const RoguelikeNodeType& type, const size_t& column, const int& y); + std::optional create_and_insert_node(RoguelikeNodeType type, const size_t& column, const int& y); void add_edge(const size_t& source, const size_t& target); void set_curr_pos(const size_t& node_index); diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.cpp index 3e8c9b52d9..6fd6062ed4 100644 --- a/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.cpp @@ -3,7 +3,6 @@ #include #include -#include "AbstractRoguelikeMap.h" #include "Config/TaskData.h" #include "Controller/Controller.h" #include "Task/ProcessTask.h" @@ -35,14 +34,6 @@ bool asst::RoguelikeRoutingTaskPlugin::load_params([[maybe_unused]] const json:: m_roi_margin = config->special_params.at(7); m_direction_threshold = config->special_params.at(8); - // 只有在界园主题时才加载 BoskyPassage 配置 - if (theme == RoguelikeTheme::JieGarden) { - const std::shared_ptr bosky_config = - Task.get(theme + "@Roguelike@RoutingBoskyPassageConfig"); - - m_bosky_config = bosky_config->special_params; - } - const RoguelikeMode& mode = m_config->get_mode(); const std::string squad = params.get("squad", ""); @@ -58,17 +49,6 @@ bool asst::RoguelikeRoutingTaskPlugin::load_params([[maybe_unused]] const json:: m_routing_strategy = RoutingStrategy::FastInvestment_JieGarden; return true; } - - if (mode == RoguelikeMode::FindPlaytime) { - m_bosky_routing_strategy = RoutingStrategy::FindPlaytime_JieGarden; - int target = m_config->get_find_playTime_target(); - RoguelikeBoskyPassageMap::get_instance().set_target_subtype(static_cast(target)); - Log.info(__FUNCTION__, "| FindPlaytime mode enabled with target:", target); - return true; - } - - m_bosky_routing_strategy = RoutingStrategy::BoskyPassage_JieGarden; - return true; } if (mode == RoguelikeMode::FastPass && squad == "蓝图测绘分队") { @@ -82,7 +62,6 @@ bool asst::RoguelikeRoutingTaskPlugin::load_params([[maybe_unused]] const json:: void asst::RoguelikeRoutingTaskPlugin::reset_in_run_variables() { m_map.reset(); - RoguelikeBoskyPassageMap::get_instance().reset(); m_need_generate_map = true; m_selected_column = 0; m_selected_x = 0; @@ -95,17 +74,6 @@ bool asst::RoguelikeRoutingTaskPlugin::verify(const AsstMsg msg, const json::val } std::string task_name = details.get("details", "task", ""); - Log.debug(__FUNCTION__, "| Checking task:", task_name); - - // 检查是否包含 BoskyPassage 并设置运行模式 - if (task_name.find("JieGarden@Roguelike@Routing-BoskyPassage") != std::string::npos) { - m_run_mode = RoguelikeRoutingTaskRunMode::BoskyPassage_JieGarden; - Log.debug(__FUNCTION__, "| Setting run mode to BoskyPassage_JieGarden"); - } - else { - m_run_mode = RoguelikeRoutingTaskRunMode::Default; - Log.debug(__FUNCTION__, "| Setting run mode to Default"); - } // trigger 任务的名字可以为 "...@Roguelike@Routing-..." 的形式 if (const size_t pos = task_name.find('-'); pos != std::string::npos) { @@ -113,7 +81,6 @@ bool asst::RoguelikeRoutingTaskPlugin::verify(const AsstMsg msg, const json::val } if (task_name == m_config->get_theme() + "@Roguelike@Routing") { - Log.info(__FUNCTION__, "| RoguelikeRoutingTaskPlugin triggered for task:", task_name); return true; } @@ -124,287 +91,97 @@ bool asst::RoguelikeRoutingTaskPlugin::_run() { LogTraceFunction; - Log.info( - __FUNCTION__, - "| Running with routing_strategy:", - static_cast(m_routing_strategy), - "bosky_routing_strategy:", - static_cast(m_bosky_routing_strategy)); - - if (m_run_mode == RoguelikeRoutingTaskRunMode::Default) { - switch (m_routing_strategy) { - case RoutingStrategy::FastInvestment_Sarkaz: - if (m_need_generate_map) { - // 随机点击一个第一列的节点,先随便写写,垃圾代码迟早要重构 - ProcessTask(*this, { "Sarkaz@Roguelike@Routing-CombatOps" }).run(); - // 刷新节点 - ProcessTask(*this, { m_config->get_theme() + "@Roguelike@RoutingRefreshNode" }).run(); - // 不识别了,进商店,Go! - Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@RoutingAction-StageTraderEnter"); - // 偷懒,直接用 m_need_generate_map 判断是否已进过商店 - m_need_generate_map = false; - } - else { - Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@RoutingAction-ExitThenAbandon"); - } - break; - case RoutingStrategy::FastInvestment_JieGarden: - if (m_need_generate_map) { - cv::Mat image = ctrler()->get_image(); - cv::Mat image_draw = image.clone(); - update_map(image, RoguelikeMap::INIT_INDEX + 1, image_draw); -#ifdef ASST_DEBUG - const std::filesystem::path& relative_dir = utils::path("debug") / utils::path("roguelikeMap"); - const auto relative_path = - relative_dir / (std::format("{}_draw.png", utils::format_now_for_filename())); - Log.debug(__FUNCTION__, "| Saving image to", relative_path); - asst::imwrite(relative_path, image_draw); -#endif - m_need_generate_map = false; - } - if (m_map.get_curr_pos() == RoguelikeMap::INIT_INDEX) { - m_map.set_cost_fun([&](const RoguelikeNodePtr& node) { - if (node->type == RoguelikeNodeType::CombatOps || node->type == RoguelikeNodeType::EmergencyOps || - node->type == RoguelikeNodeType::DreadfulFoe) { - return 1; - } - return 0; - }); - m_map.update_node_costs(); - const size_t next_node = m_map.get_next_node(); - - // 若无法避免超过两场战斗则重开 - if (m_map.get_node_cost(next_node) >= 2) { - callback( - AsstMsg::TaskChainExtraInfo, - json::object { - { "what", "RoutingRestart" }, - { "why", "TooManyBattlesAhead" }, - { "node_cost", m_map.get_node_cost(next_node) }, - }); - - Task.set_task_base( - "JieGarden@Roguelike@RoutingAction", - "JieGarden@Roguelike@RoutingAction-ExitThenAbandon"); - reset_in_run_variables(); - } - else { - const int next_node_x = m_origin_x; - const int next_node_y = m_map.get_node_y(next_node); - Point next_node_center = Point(next_node_x + m_node_width / 2, next_node_y + m_node_height / 2); - ctrler()->click(next_node_center); - sleep(200); - - Task.set_task_base( - "JieGarden@Roguelike@RoutingAction", - "JieGarden@Roguelike@RoutingAction-StageCombatOpsEnterThenLeave"); - m_map.set_curr_pos(next_node); - } - } - else { - // 执行默认的避战策略 - Task.set_task_base("JieGarden@Roguelike@RoutingAction", "JieGarden@Roguelike@Stages_default"); - } - break; - case RoutingStrategy::FastPass: - if (m_need_generate_map) { - generate_map(); - m_need_generate_map = false; - } - - m_selected_column = m_map.get_node_column(m_map.get_curr_pos()); - update_selected_x(); - - refresh_following_combat_nodes(); - navigate_route(); - break; - - default: - break; - } - } - - if (m_run_mode == RoguelikeRoutingTaskRunMode::BoskyPassage_JieGarden) { - switch (m_bosky_routing_strategy) { - case RoutingStrategy::BoskyPassage_JieGarden: { - bosky_update_map(); - const std::vector priority_order = get_bosky_passage_priority("Default"); - bosky_decide_and_click(priority_order); - break; - } - - case RoutingStrategy::FindPlaytime_JieGarden: { - // 更新地图 - bosky_update_map(); - const std::vector priority_order = get_bosky_passage_priority("FindPlaytime"); - - // 获取目标常乐节点子类型 - Log.info( - __FUNCTION__, - "| Looking for playtime subtype:", - subtype2name(RoguelikeBoskyPassageMap::get_instance().get_target_subtype())); - - // 尝试找到目标节点,使用常乐节点优先的策略 - bosky_decide_and_click(priority_order); - break; - } - - default: - break; - } - } - return true; -} - -// ==================== JieGarden BoskyPassage 平面地图逻辑 ==================== -void asst::RoguelikeRoutingTaskPlugin::bosky_update_map() -{ - LogTraceFunction; - - Log.info(__FUNCTION__, "| updating bosky map"); - - cv::Mat image = ctrler()->get_image(); - if (image.empty()) { - Log.error(__FUNCTION__, "| Failed to get image from controller"); - return; - } - - MultiMatcher node_analyzer(image); - node_analyzer.set_task_info("JieGarden@Roguelike@RoutingBoskyPassageNodeAnalyze"); - if (!node_analyzer.analyze()) { - Log.error(__FUNCTION__, "| no nodes are recognised"); - return; - } - - MultiMatcher::ResultsVec match_results = node_analyzer.get_result(); - Log.info(__FUNCTION__, "| found", match_results.size(), "nodes"); - - // 排序 靠左上优先 - sort_by_vertical_(match_results); - - const std::string& theme = m_config->get_theme(); - -#ifdef ASST_DEBUG - cv::Mat image_draw = image.clone(); -#endif - - // 处理每个识别到的节点 - for (const auto& [rect, score, templ_name] : match_results) { - Log.debug(__FUNCTION__, "| analyzing node", templ_name, "at (", rect.x, ",", rect.y, ")"); - - const RoguelikeNodeType type = RoguelikeMapInfo.templ2type(theme, templ_name); - if (type == RoguelikeNodeType::Unknown) { - Log.warn(__FUNCTION__, "| unknown template:", templ_name); - continue; - } - - // 检查是否为灰色节点 - const bool is_open = templ_name.find("Grey") == std::string::npos; - - auto idx = RoguelikeBoskyPassageMap::get_instance() - .ensure_node_from_pixel(rect.x, rect.y, m_bosky_config, is_open, type); - - if (idx.has_value()) { - // 更新节点类型(防止类型不一致) - RoguelikeBoskyPassageMap::get_instance().set_node_type(idx.value(), type); - Log.debug(__FUNCTION__, "| updated node (", idx.value(), ") type: (", type2name(type), ")"); + switch (m_routing_strategy) { + case RoutingStrategy::FastInvestment_Sarkaz: + if (m_need_generate_map) { + // 随机点击一个第一列的节点,先随便写写,垃圾代码迟早要重构 + ProcessTask(*this, { "Sarkaz@Roguelike@Routing-CombatOps" }).run(); + // 刷新节点 + ProcessTask(*this, { m_config->get_theme() + "@Roguelike@RoutingRefreshNode" }).run(); + // 不识别了,进商店,Go! + Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@RoutingAction-StageTraderEnter"); + // 偷懒,直接用 m_need_generate_map 判断是否已进过商店 + m_need_generate_map = false; } else { - Log.warn(__FUNCTION__, "| failed to create/update node from pixel (", rect.x, ",", rect.y, ")"); + Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@RoutingAction-ExitThenAbandon"); } - + break; + case RoutingStrategy::FastInvestment_JieGarden: + if (m_need_generate_map) { + cv::Mat image = ctrler()->get_image(); + cv::Mat image_draw = image.clone(); + update_map(image, RoguelikeMap::INIT_INDEX + 1, image_draw); #ifdef ASST_DEBUG - if (idx.has_value()) { - cv::rectangle(image_draw, make_rect(rect), cv::Scalar(0, 0, 255), 2); - cv::putText( - image_draw, - std::to_string(static_cast(type)) + " (" + - std::to_string(RoguelikeBoskyPassageMap::get_instance().get_node_x(idx.value())) + ", " + - std::to_string(RoguelikeBoskyPassageMap::get_instance().get_node_y(idx.value())) + ")", - cv::Point(rect.x, rect.y - 5), - cv::FONT_HERSHEY_SIMPLEX, - 0.5, - cv::Scalar(0, 0, 255), - 1); - } + const std::filesystem::path& relative_dir = utils::path("debug") / utils::path("roguelikeMap"); + const auto relative_path = relative_dir / (std::format("{}_draw.png", utils::format_now_for_filename())); + Log.trace("Save image", relative_path); + asst::imwrite(relative_path, image_draw); #endif - } - -#ifdef ASST_DEBUG - const std::filesystem::path& relative_dir = utils::path("debug") / utils::path("roguelikeMap"); - const auto relative_path = relative_dir / (std::format("{}_bosky_draw.png", utils::format_now_for_filename())); - Log.debug(__FUNCTION__, "| Saving bosky map image to", relative_path); - asst::imwrite(relative_path, image_draw); -#endif - - Log.info(__FUNCTION__, "| map updated with", RoguelikeBoskyPassageMap::get_instance().size(), "nodes"); -} - -void asst::RoguelikeRoutingTaskPlugin::bosky_decide_and_click(const std::vector& priority_order) -{ - LogTraceFunction; - - Log.info(__FUNCTION__, "| deciding and clicking a bosky passage node"); - - size_t chosen = 0; - bool found = false; - - // 按优先级顺序查找可用的节点 - for (const auto& node_type : priority_order) { - auto nodes_of_type = RoguelikeBoskyPassageMap::get_instance().get_open_unvisited_nodes(node_type); - if (!nodes_of_type.empty()) { - chosen = nodes_of_type.front(); - found = true; - Log.debug(__FUNCTION__, "| found node of type (", type2name(node_type), ") with index (", chosen, ")"); - break; + m_need_generate_map = false; } + if (m_map.get_curr_pos() == RoguelikeMap::INIT_INDEX) { + m_map.set_cost_fun([&](const RoguelikeNodePtr& node) { + if (node->type == RoguelikeNodeType::CombatOps || node->type == RoguelikeNodeType::EmergencyOps || + node->type == RoguelikeNodeType::DreadfulFoe) { + return 1; + } + return 0; + }); + m_map.update_node_costs(); + const size_t next_node = m_map.get_next_node(); + + // 若无法避免超过两场战斗则重开 + if (m_map.get_node_cost(next_node) >= 2) { + callback( + AsstMsg::TaskChainExtraInfo, + json::object { + { "what", "RoutingRestart" }, + { "why", "TooManyBattlesAhead" }, + { "node_cost", m_map.get_node_cost(next_node) }, + }); + + Task.set_task_base( + "JieGarden@Roguelike@RoutingAction", + "JieGarden@Roguelike@RoutingAction-ExitThenAbandon"); + reset_in_run_variables(); + } + else { + const int next_node_x = m_origin_x; + const int next_node_y = m_map.get_node_y(next_node); + Point next_node_center = Point(next_node_x + m_node_width / 2, next_node_y + m_node_height / 2); + ctrler()->click(next_node_center); + sleep(200); + + Task.set_task_base( + "JieGarden@Roguelike@RoutingAction", + "JieGarden@Roguelike@RoutingAction-StageCombatOpsEnterThenLeave"); + m_map.set_curr_pos(next_node); + } + } + else { + // 执行默认的避战策略 + Task.set_task_base("JieGarden@Roguelike@RoutingAction", "JieGarden@Roguelike@Stages_default"); + } + break; + case RoutingStrategy::FastPass: + if (m_need_generate_map) { + generate_map(); + m_need_generate_map = false; + } + + m_selected_column = m_map.get_node_column(m_map.get_curr_pos()); + update_selected_x(); + + refresh_following_combat_nodes(); + navigate_route(); + break; + + default: + break; } - if (!found) { - Log.info(__FUNCTION__, "| no open unvisited nodes available"); - Task.set_task_base( - "JieGarden@Roguelike@RoutingAction", - "JieGarden@Roguelike@RoutingAction-ClickRemainingCandleFlame"); - return; - } - int gx = RoguelikeBoskyPassageMap::get_instance().get_node_x(chosen); - int gy = RoguelikeBoskyPassageMap::get_instance().get_node_y(chosen); - RoguelikeNodeType node_type = RoguelikeBoskyPassageMap::get_instance().get_node_type(chosen); - - Log.info(__FUNCTION__, "| chosen node:", chosen, "(", gx, ",", gy, ") type:", type2name(node_type)); - - // 点击节点中心 - auto [px, py] = RoguelikeBoskyPassageMap::get_instance().get_node_pixel( - chosen, - m_bosky_config.origin_x, - m_bosky_config.origin_y, - m_bosky_config.column_offset, - m_bosky_config.row_offset); - - if (px == -1 || py == -1) { - Log.error(__FUNCTION__, "| Invalid pixel coordinates for node", chosen, ": (", px, ",", py, ")"); - return; - } - - Point click_point(px + m_bosky_config.node_width / 2, py + m_bosky_config.node_height / 2); - sleep(200); - ctrler()->click(click_point); - RoguelikeBoskyPassageMap::get_instance().set_visited(chosen); - RoguelikeBoskyPassageMap::get_instance().set_curr_pos(chosen); - - // 发送节点类型到 WPF - std::string node_type_name = type2name(node_type); - auto node_info = basic_info_with_what("BoskyPassageNode"); - node_info["details"]["node_type"] = node_type_name; - callback(AsstMsg::SubTaskExtraInfo, node_info); - - // 执行节点类型对应的任务 - const std::string& theme = m_config->get_theme(); - std::string node_name = type2name(node_type); - - const std::string node_task_name = theme + "@Roguelike@MapNode" + node_name; - // 设置 next - Task.set_task_base("JieGarden@Roguelike@RoutingAction", node_task_name); + return true; } bool asst::RoguelikeRoutingTaskPlugin::update_map( @@ -742,52 +519,3 @@ void asst::RoguelikeRoutingTaskPlugin::update_selected_x() m_selected_x = m_middle_x; } } - -std::vector - asst::RoguelikeRoutingTaskPlugin::get_bosky_passage_priority(const std::string& strategy) -{ - LogTraceFunction; - - const std::string& theme = m_config->get_theme(); - const std::string config_name = theme + "@Roguelike@RoutingBoskyPassagePriority_" + strategy; - - auto task_info = Task.get(config_name); - if (!task_info) { - Log.error(__FUNCTION__, "| priority config not found:", config_name); - return {}; - } - - // 从 next 字段中读取优先级配置 - const auto& next_tasks = task_info->next; - if (next_tasks.empty()) { - Log.warn(__FUNCTION__, "| Priority config is empty in:", config_name); - return {}; - } - - // 从任务名称中解析节点类型 - std::vector priority_order; - priority_order.reserve(next_tasks.size()); - - for (const std::string& task_name : next_tasks) { - // 解析类似 "JieGarden@Roguelike@MapNodeYiTrader" 这样的任务名 -> "YiTrader" - constexpr std::string_view map_node_prefix = "MapNode"; - const size_t pos = task_name.rfind(map_node_prefix); - if (pos == std::string::npos) { - Log.warn(__FUNCTION__, "| Invalid task name in priority config:", task_name); - continue; - } - - const std::string node_name = task_name.substr(pos + map_node_prefix.length()); - RoguelikeNodeType node_type = name2type(node_name); - if (node_type != RoguelikeNodeType::Unknown) { - priority_order.push_back(node_type); - Log.debug(__FUNCTION__, "| Added priority node type:", type2name(node_type), "from task:", task_name); - } - else { - Log.warn(__FUNCTION__, "| Failed to parse node type from task:", task_name); - } - } - - Log.info(__FUNCTION__, "| Loaded", priority_order.size(), "node types from priority config"); - return priority_order; -} diff --git a/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h b/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h index 9e222774aa..de941f62a9 100644 --- a/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h +++ b/src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h @@ -1,8 +1,7 @@ #pragma once #include "Common/AsstTypes.h" -#include "Config/Roguelike/RoguelikeMapConfig.h" -#include "RoguelikeBoskyPassageMap.h" +#include "RoguelikeMap.h" #include "Task/Roguelike/AbstractRoguelikeTaskPlugin.h" #include "Utils/NoWarningCVMat.h" @@ -23,14 +22,6 @@ public: FastInvestment_Sarkaz, FastInvestment_JieGarden, FastPass, - BoskyPassage_JieGarden, - FindPlaytime_JieGarden - }; - - enum class RoguelikeRoutingTaskRunMode - { - Default, // 普通肉鸽地图 - BoskyPassage_JieGarden, // 界园树洞地图 }; protected: @@ -68,11 +59,7 @@ private: // ———————— constants and variables ——————————————————————————————————————————————— RoutingStrategy m_routing_strategy = RoutingStrategy::None; - RoutingStrategy m_bosky_routing_strategy = RoutingStrategy::None; RoguelikeMap m_map; - // 界园树洞地图使用单例模式,通过 RoguelikeBoskyPassageMap::get_instance() 访问 - // 运行模式 - mutable RoguelikeRoutingTaskRunMode m_run_mode = RoguelikeRoutingTaskRunMode::Default; bool m_need_generate_map = true; size_t m_selected_column = 0; // 当前选中节点所在列 int m_selected_x = 0; // 当前选中节点的横坐标 (Rect.x) @@ -86,12 +73,5 @@ private: int m_nameplate_offset = 0; // 节点 Rect 下边缘到节点铭牌下边缘的距离 int m_roi_margin = 0; // roi 的 margin offset int m_direction_threshold = 0; // 节点间连线方向判定的阈值 - - // ==================== BoskyPassage (JieGarden) 专用 ==================== - void bosky_update_map(); // 从当前截图识别所有可见节点并更新/创建 - void bosky_decide_and_click(const std::vector& priority_order); // 策略,可指定优先级策略 - std::vector get_bosky_passage_priority(const std::string& strategy); // 从配置文件读取优先级 - - RoguelikeBoskyPassageMap::BoskyPassageMapConfig m_bosky_config; }; }