refactor: 拆出界园岁兽残识地图导航 (#14432)

* refactor: 舍弃 AbstractRoguelikeMap,拆到 RoguelikeMapConfig 与 RoguelikeBoskyPassageMap

* refactor: 拆出界园岁兽残识地图导航
This commit is contained in:
Weiyou Wang
2025-10-18 20:15:36 +11:00
committed by GitHub
parent 6733f49d8b
commit 11f9d6e0a4
16 changed files with 583 additions and 536 deletions

View File

@@ -1,6 +1,6 @@
{
"theme": "JieGarden",
"node": [
"nodes": [
{
"type": "CombatOps",
"template": ["JieGarden@Roguelike@MapNodeCombatOps.png", "JieGarden@Roguelike@MapNodeCombatOpsGrey.png"]

View File

@@ -1,6 +1,6 @@
{
"theme": "Sarkaz",
"node": [
"nodes": [
{
"type": "CombatOps",
"template": ["Sarkaz@Roguelike@MapNodeCombatOps.png", "Sarkaz@Roguelike@MapNodeCombatOpsGrey.png"]

View File

@@ -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": {

View File

@@ -4,24 +4,100 @@
#include "Utils/Logger.hpp"
bool asst::RoguelikeMapConfig::parse(const json::value& json)
using namespace asst;
static const std::unordered_map<std::string, RoguelikeNodeType> 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<RoguelikeNodeType, std::string> NODE_NAME_MAPPING = []() {
std::unordered_map<RoguelikeNodeType, std::string> 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<int>(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;
}

View File

@@ -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<RoguelikeMapConfig>, 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<std::string, std::unordered_map<std::string, RoguelikeNodeType>> m_templ_type_mappings;
};

View File

@@ -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<RoguelikeCollapsalParadigmTaskPlugin>(m_config_ptr, m_control_ptr);
// ------------------ 萨卡兹主题专用插件 ------------------
m_roguelike_task_ptr->register_plugin<RoguelikeRoutingTaskPlugin>(m_config_ptr, m_control_ptr);
m_roguelike_task_ptr->register_plugin<RoguelikeInputSeedTaskPlugin>(m_config_ptr, m_control_ptr);
// ------------------ 导航相关插件 ------------------
m_roguelike_task_ptr->register_plugin<RoguelikeRoutingTaskPlugin>(m_config_ptr, m_control_ptr);
m_roguelike_task_ptr->register_plugin<RoguelikeBoskyPassageRoutingTaskPlugin>(m_config_ptr, m_control_ptr);
m_subtasks.emplace_back(m_roguelike_task_ptr);
}

View File

@@ -1,72 +0,0 @@
#include "AbstractRoguelikeMap.h"
#include "RoguelikeBoskyPassageMap.h"
#include <unordered_map>
namespace asst
{
// 共用的节点类型映射表
static const std::unordered_map<std::string, RoguelikeNodeType> 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<RoguelikeNodeType, std::string> reverse_mapping = []() {
std::unordered_map<RoguelikeNodeType, std::string> 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<RoguelikeBoskySubNodeType, std::string> 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

View File

@@ -1,54 +0,0 @@
#pragma once
#include <string>
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

View File

@@ -1,11 +1,27 @@
#include "RoguelikeBoskyPassageMap.h"
#include "AbstractRoguelikeMap.h"
#include "Utils/Logger.hpp"
#include <array>
#include "Utils/Logger.hpp"
namespace asst
{
std::string subtype2name(RoguelikeBoskySubNodeType node_subtype)
{
// 界园树洞子节点类型映射表
static const std::unordered_map<RoguelikeBoskySubNodeType, std::string> 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<size_t>
RoguelikeBoskyPassageMap::create_and_insert_node(int x, int y, RoguelikeNodeType type, bool is_open)
{

View File

@@ -1,12 +1,12 @@
#pragma once
#include "AbstractRoguelikeMap.h"
#include "Utils/SingletonHolder.hpp"
#include <array>
#include <optional>
#include <vector>
#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<RoguelikeBoskyPassageMap>
{
public:

View File

@@ -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<MatchTaskInfo> bosky_config =
Task.get<MatchTaskInfo>("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<RoguelikeBoskySubNodeType>(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<int>(m_bosky_routing_strategy));
switch (m_bosky_routing_strategy) {
case RoutingStrategy::BoskyPassage_JieGarden: {
bosky_update_map();
const std::vector<RoguelikeNodeType> priority_order = get_bosky_passage_priority("Default");
bosky_decide_and_click(priority_order);
break;
}
case RoutingStrategy::FindPlaytime_JieGarden: {
// 更新地图
bosky_update_map();
const std::vector<RoguelikeNodeType> 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<cv::Rect>(rect), cv::Scalar(0, 0, 255), 2);
cv::putText(
image_draw,
std::to_string(static_cast<int>(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<RoguelikeNodeType>& 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::RoguelikeNodeType>
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<RoguelikeNodeType> 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;
}

View File

@@ -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<RoguelikeNodeType>& priority_order); // 策略,可指定优先级策略
std::vector<RoguelikeNodeType> get_bosky_passage_priority(const std::string& strategy); // 从配置文件读取优先级
inline static std::function<std::string(RoguelikeNodeType)> type2name = &RoguelikeMapConfig::type2name;
inline static std::function<RoguelikeNodeType(const std::string&)> name2type = &RoguelikeMapConfig::name2type;
// ———————— constants and variables ———————————————————————————————————————————————
RoutingStrategy m_bosky_routing_strategy = RoutingStrategy::None;
RoguelikeBoskyPassageMap::BoskyPassageMapConfig m_bosky_config;
// 界园树洞地图使用单例模式,通过 RoguelikeBoskyPassageMap::get_instance() 访问
};
}

View File

@@ -16,7 +16,7 @@ asst::RoguelikeMap::RoguelikeMap()
// ————————————————————————————————————————————————————————————————————————————————
std::optional<size_t>
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<RoguelikeNode>(type, column, y);
return insert_node(node, column);

View File

@@ -1,12 +1,12 @@
#pragma once
#include "AbstractRoguelikeMap.h"
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#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<size_t> create_and_insert_node(const RoguelikeNodeType& type, const size_t& column, const int& y);
std::optional<size_t> 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);

View File

@@ -3,7 +3,6 @@
#include <limits>
#include <numeric>
#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<MatchTaskInfo> bosky_config =
Task.get<MatchTaskInfo>(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<RoguelikeBoskySubNodeType>(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<int>(m_routing_strategy),
"bosky_routing_strategy:",
static_cast<int>(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<RoguelikeNodeType> priority_order = get_bosky_passage_priority("Default");
bosky_decide_and_click(priority_order);
break;
}
case RoutingStrategy::FindPlaytime_JieGarden: {
// 更新地图
bosky_update_map();
const std::vector<RoguelikeNodeType> 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<cv::Rect>(rect), cv::Scalar(0, 0, 255), 2);
cv::putText(
image_draw,
std::to_string(static_cast<int>(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<RoguelikeNodeType>& 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::RoguelikeNodeType>
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<RoguelikeNodeType> 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;
}

View File

@@ -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<RoguelikeNodeType>& priority_order); // 策略,可指定优先级策略
std::vector<RoguelikeNodeType> get_bosky_passage_priority(const std::string& strategy); // 从配置文件读取优先级
RoguelikeBoskyPassageMap::BoskyPassageMapConfig m_bosky_config;
};
}