mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-18 18:20:39 +08:00
refactor: 拆出界园岁兽残识地图导航 (#14432)
* refactor: 舍弃 AbstractRoguelikeMap,拆到 RoguelikeMapConfig 与 RoguelikeBoskyPassageMap * refactor: 拆出界园岁兽残识地图导航
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"theme": "JieGarden",
|
||||
"node": [
|
||||
"nodes": [
|
||||
{
|
||||
"type": "CombatOps",
|
||||
"template": ["JieGarden@Roguelike@MapNodeCombatOps.png", "JieGarden@Roguelike@MapNodeCombatOpsGrey.png"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"theme": "Sarkaz",
|
||||
"node": [
|
||||
"nodes": [
|
||||
{
|
||||
"type": "CombatOps",
|
||||
"template": ["Sarkaz@Roguelike@MapNodeCombatOps.png", "Sarkaz@Roguelike@MapNodeCombatOpsGrey.png"]
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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() 访问
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user