feat: 添加刷常乐节点策略 (#13868)

* feat: 支持识别常乐节点的子类型

* perf: 将树洞节点优先级拆到配置里

* refactor: 将界园树洞策略从通用Routing策略中分离出来

* feat: 初步实现刷常乐节点

* feat: 为刷常乐节点功能添加UI

* refactor: 重命名节点类型获取函数

* perf: 优化节点类型的日志输出

* feat: 添加发现目标常乐节点后退出

* i18n: CN常乐节点显示文本

* fix: 修复type2name映射初始化潜在问题

* perf: 添加目标子类型的设置和获取方法

* refactor: 优化优先级节点类型解析逻辑

* feat: 刷常乐节点策略下保存路上的招募券

* i18n: 为其它语言提供占位

* perf: 将新增的本地化键值放到一起

* chore: 调整日志输出中的空格

* perf: 仅在非开局招募时保存招募券

* perf: 保留招募券到达上限时正常招募

* fix: 修复gui日志输出没有使用本地化

* fix: 给界园树洞节点点击加上200ms的前置延时

* perf: 在 RoguelikeRoutingTaskPlugin 中添加运行模式以区分普通地图与界园树洞

* refactor: 优化RoguelikeBoskyPassageMap的单例使用

* feat: 添加界园树洞节点类型wpf输出及其颜色配置

* fix: 不在每次开始肉鸽重置m_target_subtype
This commit is contained in:
travellerse
2025-10-17 13:54:30 +08:00
committed by GitHub
parent 724130da50
commit dce6fbfbce
27 changed files with 816 additions and 202 deletions

View File

@@ -506,6 +506,94 @@
"template": "empty.png",
"specialParams": [188, 107, 608, 321, 1028, 535, 64, 57, 140, 107, 15]
},
"JieGarden@Roguelike@RoutingBoskyPassagePriority_Default": {
"doc": [
"界园树洞节点优先级策略配置",
"按优先级从高到低排列节点类型名称",
"拾遗>传说>故肆>易与>筹谋>常乐>杂疑(暂时取消)>灾祸(暂时取消)"
],
"template": "empty.png",
"next": [
"JieGarden@Roguelike@MapNodeOmissions",
"JieGarden@Roguelike@MapNodeLegend",
"JieGarden@Roguelike@MapNodeOldShop",
"JieGarden@Roguelike@MapNodeYiTrader",
"JieGarden@Roguelike@MapNodeScheme",
"JieGarden@Roguelike@MapNodePlaytime"
]
},
"JieGarden@Roguelike@RoutingBoskyPassagePriority_FindPlaytime": {
"doc": ["常乐>拾遗>传说>故肆>易与>筹谋>杂疑(暂时取消)>灾祸(暂时取消)"],
"template": "empty.png",
"next": [
"JieGarden@Roguelike@MapNodePlaytime",
"JieGarden@Roguelike@MapNodeOmissions",
"JieGarden@Roguelike@MapNodeLegend",
"JieGarden@Roguelike@MapNodeOldShop",
"JieGarden@Roguelike@MapNodeYiTrader",
"JieGarden@Roguelike@MapNodeScheme"
]
},
"JieGarden@Roguelike@MapNodeYiTrader": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageYiTraderEnter"]
},
"JieGarden@Roguelike@MapNodeDisaster": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageCombatDpsEnter"]
},
"JieGarden@Roguelike@MapNodeOmissions": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageEncounterEnter"]
},
"JieGarden@Roguelike@MapNodeLegend": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageEncounterEnter"]
},
"JieGarden@Roguelike@MapNodeOldShop": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageEncounterEnter"]
},
"JieGarden@Roguelike@MapNodeScheme": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageEncounterEnter"]
},
"JieGarden@Roguelike@MapNodePlaytime": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageEncounterEnter"]
},
"JieGarden@Roguelike@MapNodeDoubts": {
"template": "empty.png",
"next": ["JieGarden@Roguelike@RoutingAction-StageEncounterEnter"]
},
"JieGarden@Roguelike@ReserveRecruitmentVoucher": {
"doc": "保留招募券 - hsv用来区分是否到达保留上限",
"algorithm": "MatchTemplate",
"method": "HSVCount",
"roi": [948, 5, 229, 56],
"colorScales": [
[
[0, 0, 50],
[10, 255, 210]
],
[
[170, 0, 50],
[180, 255, 210]
],
[
[0, 0, 230],
[180, 30, 255]
]
],
"action": "ClickSelf",
"next": ["JieGarden@Roguelike@ReserveRecruitmentVoucherConfirm", "#self"]
},
"JieGarden@Roguelike@ReserveRecruitmentVoucherConfirm": {
"algorithm": "OcrDetect",
"text": ["确认留存"],
"roi": [766, 572, 142, 49],
"action": "ClickSelf"
},
"JieGarden@Roguelike@RoutingBoskyPassageNodeAnalyze": {
"template": [
"JieGarden@Roguelike@MapNodeOldShop.png",
@@ -1195,6 +1283,22 @@
"binThreshold": [200, 255],
"useRaw": false
},
"JieGarden@Roguelike@StrategyChange_mode20001": {
"text": ["_default", "_restart", "_boskyPassageDefault"],
"ocrReplace": [
["洪陆楼", "_default"],
["山水阁", "_restart"],
["云(瓦)?(亭|亨)", "_restart"],
["汝吾(门)?", "_restart"],
["见字祠", "_restart"],
["始末陵", "_restart"],
["是非境", "_boskyPassageDefault"]
],
"roi": [627, 14, 71, 28],
"withoutDet": true,
"binThreshold": [200, 255],
"useRaw": false
},
"JieGarden@Roguelike@StrategyChange_mode4": {
"text": ["_default", "_exit", "_leaveBoskyPassage"],
"ocrReplace": [

View File

@@ -1742,6 +1742,9 @@
"Roguelike@StrategyChange_mode10001": {
"baseTask": "Roguelike@StrategyChange_default"
},
"Roguelike@StrategyChange_mode20001": {
"baseTask": "Roguelike@StrategyChange_default"
},
"Roguelike@StrategyChange_mode4": {
"baseTask": "Roguelike@StrategyChange_default"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -8,39 +8,11 @@ bool asst::RoguelikeMapConfig::parse(const json::value& json)
{
LogTraceFunction;
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 }
};
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 = NodeTypeMapping.at(node_json.at("type").as_string());
const RoguelikeNodeType node_type = name2type(node_json.at("type").as_string());
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);

View File

@@ -0,0 +1,72 @@
#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,10 +1,12 @@
#pragma once
#include <optional>
#include <vector>
#include <string>
namespace asst
{
enum class RoguelikeBoskySubNodeType;
enum class RoguelikeNodeType
{
Init = -1, // Init
@@ -39,4 +41,14 @@ enum class RoguelikeNodeType
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,21 +1,16 @@
#include "RoguelikeBoskyPassageMap.h"
#include "AbstractRoguelikeMap.h"
#include "Utils/Logger.hpp"
#include <array>
namespace asst
{
RoguelikeBoskyPassageMap::RoguelikeBoskyPassageMap()
{
reset();
}
std::optional<size_t>
RoguelikeBoskyPassageMap::create_and_insert_node(int x, int y, RoguelikeNodeType type, bool is_open)
{
if (!in_bounds(x, y)) {
Log.warn(__FUNCTION__, "| Coordinates (", x, ", ", y, ") out of bounds");
Log.warn(__FUNCTION__, "| Coordinates (", x, ",", y, ") out of bounds");
return std::nullopt;
}
@@ -36,7 +31,7 @@ std::optional<size_t>
return idx;
}
else {
Log.warn(__FUNCTION__, "| Node already exists at (", x, ", ", y, ")");
Log.warn(__FUNCTION__, "| Node already exists at (", x, ",", y, ")");
return std::nullopt;
}
}
@@ -68,11 +63,35 @@ void RoguelikeBoskyPassageMap::set_node_type(size_t index, RoguelikeNodeType typ
n->type = type;
}
void RoguelikeBoskyPassageMap::set_node_subtype(size_t index, RoguelikeBoskySubNodeType sub_type)
{
Node* n = get_valid_node(index);
if (!n || !n->exists) {
Log.warn(__FUNCTION__, "| Invalid index or node doesn't exist:", index);
return;
}
n->sub_type = sub_type;
Log.info(__FUNCTION__, "| Set node", index, "subtype to", subtype2name(sub_type));
}
RoguelikeBoskySubNodeType RoguelikeBoskyPassageMap::get_node_subtype(size_t index) const
{
if (index >= m_nodes.size()) {
return RoguelikeBoskySubNodeType::Unknown;
}
const Node& n = m_nodes[index];
if (!n.exists) {
return RoguelikeBoskySubNodeType::Unknown;
}
return n.sub_type;
}
void RoguelikeBoskyPassageMap::reset()
{
for (auto& n : m_nodes) {
n = Node {}; // reset
}
m_curr_pos = 0;
m_existing_count = 0;
}
@@ -213,10 +232,10 @@ std::optional<size_t> RoguelikeBoskyPassageMap::ensure_node_from_pixel(
}
auto [gx, gy] = pixel_to_grid_coords(px, py, config);
Log.info(__FUNCTION__, "| analyzing node (", px, ", ", py, ") -> (", gx, ", ", gy, ")");
Log.info(__FUNCTION__, "| analyzing node (", px, ",", py, ") -> (", gx, ",", gy, ")");
if (!in_bounds(gx, gy)) {
Log.warn(__FUNCTION__, "| Grid coordinates (", gx, ", ", gy, ") out of bounds");
Log.warn(__FUNCTION__, "| Grid coordinates (", gx, ",", gy, ") out of bounds");
return std::nullopt;
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "AbstractRoguelikeMap.h"
#include "Utils/SingletonHolder.hpp"
#include <array>
#include <optional>
@@ -8,10 +9,18 @@
namespace asst
{
class RoguelikeBoskyPassageMap
enum class RoguelikeBoskySubNodeType
{
Unknown = 0,
Ling = 1, // 令 - 常乐 掷地有声
Shu = 2, // 黍 - 常乐 种因得果
Nian = 3, // 年 - 常乐 三缺一
};
class RoguelikeBoskyPassageMap : public SingletonHolder<RoguelikeBoskyPassageMap>
{
public:
RoguelikeBoskyPassageMap();
~RoguelikeBoskyPassageMap() = default;
// ===================== 创建/更新 =====================
@@ -24,6 +33,8 @@ public:
void set_node_type(size_t index, RoguelikeNodeType type);
void set_curr_pos(size_t index) { m_curr_pos = index; }
void reset();
// ===================== 获取信息 =====================
@@ -39,6 +50,8 @@ public:
// 全部开放节点(包含已通关)
[[nodiscard]] std::vector<size_t> get_open_nodes(std::optional<RoguelikeNodeType> type_filter = std::nullopt) const;
[[nodiscard]] size_t get_curr_pos() const { return m_curr_pos; }
[[nodiscard]] RoguelikeNodeType get_node_type(size_t index) const;
[[nodiscard]] bool get_node_exists(size_t index) const;
[[nodiscard]] bool get_node_open(size_t index) const;
@@ -102,6 +115,18 @@ public:
bool is_open,
RoguelikeNodeType type = RoguelikeNodeType::Unknown);
// 设置子类型
void set_node_subtype(size_t index, RoguelikeBoskySubNodeType sub_type);
// 获取子类型
[[nodiscard]] RoguelikeBoskySubNodeType get_node_subtype(size_t index) const;
// 设置目标子类型
void set_target_subtype(RoguelikeBoskySubNodeType target) { m_target_subtype = target; }
// 获取目标子类型
[[nodiscard]] RoguelikeBoskySubNodeType get_target_subtype() const { return m_target_subtype; }
// 中心节点 index
static constexpr int CENTER_X = 3;
static constexpr int CENTER_Y = 2;
@@ -117,13 +142,16 @@ private:
struct Node
{
RoguelikeNodeType type = RoguelikeNodeType::Unknown;
RoguelikeBoskySubNodeType sub_type = RoguelikeBoskySubNodeType::Unknown;
bool exists = false; // 是否已创建
bool is_open = false; // 是否开放可探索
bool visited = false; // 是否已访问
};
std::array<Node, WIDTH * HEIGHT> m_nodes {}; // 直接按 y*WIDTH + x 索引
size_t m_curr_pos = 0;
size_t m_existing_count = 0;
RoguelikeBoskySubNodeType m_target_subtype = RoguelikeBoskySubNodeType::Unknown;
// 辅助函数:获取有效的节点引用,如果索引无效或节点不存在则返回 nullptr
[[nodiscard]] Node* get_valid_node(size_t index);

View File

@@ -58,10 +58,17 @@ bool asst::RoguelikeRoutingTaskPlugin::load_params([[maybe_unused]] const json::
m_routing_strategy = RoutingStrategy::FastInvestment_JieGarden;
return true;
}
else {
m_routing_strategy = RoutingStrategy::BoskyPassage_JieGarden;
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 == "蓝图测绘分队") {
@@ -75,7 +82,7 @@ bool asst::RoguelikeRoutingTaskPlugin::load_params([[maybe_unused]] const json::
void asst::RoguelikeRoutingTaskPlugin::reset_in_run_variables()
{
m_map.reset();
m_bosky_map.reset();
RoguelikeBoskyPassageMap::get_instance().reset();
m_need_generate_map = true;
m_selected_column = 0;
m_selected_x = 0;
@@ -88,6 +95,17 @@ 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) {
@@ -95,6 +113,7 @@ 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;
}
@@ -105,102 +124,135 @@ bool asst::RoguelikeRoutingTaskPlugin::_run()
{
LogTraceFunction;
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();
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_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();
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 {
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);
Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@RoutingAction-ExitThenAbandon");
}
}
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;
}
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();
m_selected_column = m_map.get_node_column(m_map.get_curr_pos());
update_selected_x();
// 若无法避免超过两场战斗则重开
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) },
});
refresh_following_combat_nodes();
navigate_route();
break;
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);
case RoutingStrategy::BoskyPassage_JieGarden: {
bosky_update_map();
bosky_decide_and_click();
break;
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;
}
}
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;
}
@@ -225,7 +277,7 @@ void asst::RoguelikeRoutingTaskPlugin::bosky_update_map()
}
MultiMatcher::ResultsVec match_results = node_analyzer.get_result();
Log.info(__FUNCTION__, "| found ", match_results.size(), " nodes");
Log.info(__FUNCTION__, "| found", match_results.size(), "nodes");
// 排序 靠左上优先
sort_by_vertical_(match_results);
@@ -238,26 +290,27 @@ void asst::RoguelikeRoutingTaskPlugin::bosky_update_map()
// 处理每个识别到的节点
for (const auto& [rect, score, templ_name] : match_results) {
Log.debug(__FUNCTION__, "| analyzing node ", templ_name, " at (", rect.x, ", ", rect.y, ")");
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);
Log.warn(__FUNCTION__, "| unknown template:", templ_name);
continue;
}
// 检查是否为灰色节点
const bool is_open = templ_name.find("Grey") == std::string::npos;
auto idx = m_bosky_map.ensure_node_from_pixel(rect.x, rect.y, m_bosky_config, is_open, type);
auto idx = RoguelikeBoskyPassageMap::get_instance()
.ensure_node_from_pixel(rect.x, rect.y, m_bosky_config, is_open, type);
if (idx.has_value()) {
// 更新节点类型(防止类型不一致)
m_bosky_map.set_node_type(idx.value(), type);
Log.debug(__FUNCTION__, "| updated node (", idx.value(), ") type: (", static_cast<int>(type), ")");
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, ")");
Log.warn(__FUNCTION__, "| failed to create/update node from pixel (", rect.x, ",", rect.y, ")");
}
#ifdef ASST_DEBUG
@@ -265,8 +318,9 @@ void asst::RoguelikeRoutingTaskPlugin::bosky_update_map()
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(m_bosky_map.get_node_x(idx.value())) +
", " + std::to_string(m_bosky_map.get_node_y(idx.value())) + ")",
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,
@@ -279,47 +333,29 @@ void asst::RoguelikeRoutingTaskPlugin::bosky_update_map()
#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);
Log.debug(__FUNCTION__, "| Saving bosky map image to", relative_path);
asst::imwrite(relative_path, image_draw);
#endif
Log.info(__FUNCTION__, "| map updated with ", m_bosky_map.size(), " nodes");
Log.info(__FUNCTION__, "| map updated with", RoguelikeBoskyPassageMap::get_instance().size(), "nodes");
}
void asst::RoguelikeRoutingTaskPlugin::bosky_decide_and_click()
void asst::RoguelikeRoutingTaskPlugin::bosky_decide_and_click(const std::vector<RoguelikeNodeType>& priority_order)
{
LogTraceFunction;
Log.info(__FUNCTION__, "| deciding and clicking a bosky passage node");
// 定义节点类型的优先级顺序
const std::vector<RoguelikeNodeType> priority_order = {
RoguelikeNodeType::Omissions, // 拾遗 - 最高优先级
RoguelikeNodeType::Legend, // 传说
RoguelikeNodeType::YiTrader, // 易与
RoguelikeNodeType::OldShop, // 故肆
RoguelikeNodeType::Scheme, // 筹谋
RoguelikeNodeType::Playtime, // 常乐
RoguelikeNodeType::Doubts, // 杂疑
// RoguelikeNodeType::Disaster // 祸乱 - 最低优先级
};
size_t chosen = 0;
bool found = false;
// 按优先级顺序查找可用的节点
for (const auto& node_type : priority_order) {
auto nodes_of_type = m_bosky_map.get_open_unvisited_nodes(node_type);
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 (",
static_cast<int>(node_type),
") with index (",
chosen,
")");
Log.debug(__FUNCTION__, "| found node of type (", type2name(node_type), ") with index (", chosen, ")");
break;
}
}
@@ -331,14 +367,14 @@ void asst::RoguelikeRoutingTaskPlugin::bosky_decide_and_click()
"JieGarden@Roguelike@RoutingAction-ClickRemainingCandleFlame");
return;
}
int gx = m_bosky_map.get_node_x(chosen);
int gy = m_bosky_map.get_node_y(chosen);
RoguelikeNodeType node_type = m_bosky_map.get_node_type(chosen);
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: ", static_cast<int>(node_type));
Log.info(__FUNCTION__, "| chosen node:", chosen, "(", gx, ",", gy, ") type:", type2name(node_type));
// 点击节点中心
auto [px, py] = m_bosky_map.get_node_pixel(
auto [px, py] = RoguelikeBoskyPassageMap::get_instance().get_node_pixel(
chosen,
m_bosky_config.origin_x,
m_bosky_config.origin_y,
@@ -346,38 +382,29 @@ void asst::RoguelikeRoutingTaskPlugin::bosky_decide_and_click()
m_bosky_config.row_offset);
if (px == -1 || py == -1) {
Log.error(__FUNCTION__, "| Invalid pixel coordinates for node ", chosen, ": (", px, ", ", py, ")");
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);
m_bosky_map.set_visited(chosen);
RoguelikeBoskyPassageMap::get_instance().set_visited(chosen);
RoguelikeBoskyPassageMap::get_instance().set_curr_pos(chosen);
// 根据节点类型判断 Task.set_task_base base_task_name
switch (m_bosky_map.get_node_type(chosen)) {
case RoguelikeNodeType::YiTrader:
Task.set_task_base("JieGarden@Roguelike@RoutingAction", "JieGarden@Roguelike@RoutingAction-StageYiTraderEnter");
break;
case RoguelikeNodeType::Disaster:
Task.set_task_base(
"JieGarden@Roguelike@RoutingAction",
"JieGarden@Roguelike@RoutingAction-StageCombatDpsEnter");
break;
case RoguelikeNodeType::Omissions:
case RoguelikeNodeType::Legend:
case RoguelikeNodeType::OldShop:
case RoguelikeNodeType::Scheme:
case RoguelikeNodeType::Playtime:
case RoguelikeNodeType::Doubts:
Task.set_task_base(
"JieGarden@Roguelike@RoutingAction",
"JieGarden@Roguelike@RoutingAction-StageEncounterEnter");
break;
default:
Log.warn(__FUNCTION__, "| unknown node type: ", static_cast<int>(node_type));
break;
}
// 发送节点类型到 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);
}
bool asst::RoguelikeRoutingTaskPlugin::update_map(
@@ -715,3 +742,52 @@ 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

@@ -23,7 +23,14 @@ public:
FastInvestment_Sarkaz,
FastInvestment_JieGarden,
FastPass,
BoskyPassage_JieGarden
BoskyPassage_JieGarden,
FindPlaytime_JieGarden
};
enum class RoguelikeRoutingTaskRunMode
{
Default, // 普通肉鸽地图
BoskyPassage_JieGarden, // 界园树洞地图
};
protected:
@@ -61,9 +68,11 @@ private:
// ———————— constants and variables ———————————————————————————————————————————————
RoutingStrategy m_routing_strategy = RoutingStrategy::None;
RoutingStrategy m_bosky_routing_strategy = RoutingStrategy::None;
RoguelikeMap m_map;
// 界园树洞平面地图
RoguelikeBoskyPassageMap m_bosky_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)
@@ -79,8 +88,9 @@ private:
int m_direction_threshold = 0; // 节点间连线方向判定的阈值
// ==================== BoskyPassage (JieGarden) 专用 ====================
void bosky_update_map(); // 从当前截图识别所有可见节点并更新/创建
void bosky_decide_and_click(); // 策略
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;
};

View File

@@ -82,6 +82,10 @@ bool asst::RoguelikeConfig::verify_and_load_params(const json::value& params)
// 启用特殊策略,联动 RoguelikeRoutingTaskPlugin
Task.set_task_base(strategy_task, "JieGarden@Roguelike@StrategyChange_mode4-FastPass");
}
if (m_mode == RoguelikeMode::FindPlaytime) {
// 启用刷常乐节点策略,联动 RoguelikeRoutingTaskPlugin
Task.set_task_base(strategy_task, "JieGarden@Roguelike@StrategyChange_mode20001");
}
}
}
@@ -101,6 +105,14 @@ bool asst::RoguelikeConfig::verify_and_load_params(const json::value& params)
m_run_for_collectible = true; // 烧开水模式下,如果不是只凹直升,第一轮游戏先烧水
}
if (m_mode == RoguelikeMode::FindPlaytime) {
m_find_playTime_target = params.get("find_playTime_target", 0);
if (m_find_playTime_target < 1 || m_find_playTime_target > 3) {
Log.error(__FUNCTION__, "| Invalid find_playTime_target", m_find_playTime_target);
return false;
}
}
return true;
}

View File

@@ -34,7 +34,10 @@ enum class RoguelikeMode
CLP_PDS = 5, // 5 - 刷隐藏坍缩范式,以增加坍缩值为最优先目标
// ------------------ 萨卡兹主题专用模式 ------------------
FastPass = 10001 // 10001 - 快速通过第一层
FastPass = 10001, // 10001 - 快速通过第一层
// ------------------ 界园主题专用模式 ------------------
FindPlaytime = 20001 // 20001 - 刷常乐节点,第一层进洞,找不到需要的节点就重开
};
struct RoguelikeOper
@@ -92,7 +95,8 @@ public:
return mode == RoguelikeMode::Exp || mode == RoguelikeMode::Investment || mode == RoguelikeMode::Collectible ||
mode == RoguelikeMode::Squad || mode == RoguelikeMode::Exploration ||
(mode == RoguelikeMode::CLP_PDS && theme == RoguelikeTheme::Sami) ||
(mode == RoguelikeMode::FastPass && theme == RoguelikeTheme::Sarkaz);
(mode == RoguelikeMode::FastPass && theme == RoguelikeTheme::Sarkaz) ||
(mode == RoguelikeMode::FindPlaytime && theme == RoguelikeTheme::JieGarden);
}
bool verify_and_load_params(const json::value& params);
@@ -134,6 +138,11 @@ public:
bool get_collectible_mode_shopping() const { return m_collectible_mode_shopping; }
// ------------------ 刷常乐节点模式 ------------------
void set_find_playTime_target(int target) { m_find_playTime_target = target; }
int get_find_playTime_target() const { return m_find_playTime_target; }
private:
std::string m_theme; // 主题
RoguelikeMode m_mode = RoguelikeMode::Exp; // 模式
@@ -151,6 +160,9 @@ private:
// ------------------ 刷开局模式 ------------------
bool m_collectible_mode_shopping = false; // 刷开局模式下进入商店时购物
// ------------------ 刷常乐节点模式 ------------------
int m_find_playTime_target = 0; // 目标常乐节点子类型 (1=令, 2=黍, 3=年)
private:
// =========================== 萨米主题专用参数 ===========================
public:

View File

@@ -101,6 +101,15 @@ bool asst::RoguelikeRecruitTaskPlugin::_run()
}
}
// 刷常乐节点保存路上招募券
// 只在非开局招募时保存招募券
if (theme == RoguelikeTheme::JieGarden && mode == RoguelikeMode::FindPlaytime && !m_initail_recruit) {
bool ret = ProcessTask(*this, { "JieGarden@Roguelike@ReserveRecruitmentVoucher" }).run();
if (ret) {
return true;
}
}
if (m_initail_recruit && m_recruit_count == 1) {
if (m_config->get_use_support()) { // 是否使用助战干员开局
if (recruit_support_char()) {

View File

@@ -3,6 +3,7 @@
#include "Config/Roguelike/RoguelikeStageEncounterConfig.h"
#include "Controller/Controller.h"
#include "Task/ProcessTask.h"
#include "Task/Roguelike/Map/RoguelikeBoskyPassageMap.h"
#include "Utils/Logger.hpp"
#include "Utils/NoWarningCV.h"
#include "Vision/Matcher.h"
@@ -131,6 +132,35 @@ std::optional<std::string> asst::RoguelikeStageEncounterTaskPlugin::handle_singl
}
}
// 界园树洞
if (m_config->get_theme() == RoguelikeTheme::JieGarden) {
auto& bosky_map = RoguelikeBoskyPassageMap::get_instance();
if (event.name == "掷地有声") {
bosky_map.set_node_subtype(bosky_map.get_curr_pos(), RoguelikeBoskySubNodeType::Ling);
}
else if (event.name == "种因得果") {
bosky_map.set_node_subtype(bosky_map.get_curr_pos(), RoguelikeBoskySubNodeType::Shu);
}
else if (event.name == "三缺一") {
bosky_map.set_node_subtype(bosky_map.get_curr_pos(), RoguelikeBoskySubNodeType::Nian);
}
if (bosky_map.get_target_subtype() != RoguelikeBoskySubNodeType::Unknown) {
if (bosky_map.get_node_subtype(bosky_map.get_curr_pos()) == bosky_map.get_target_subtype()) {
Log.info(__FUNCTION__, "| Found target playtime node, completing task and exiting");
auto target_info = basic_info_with_what("RoguelikeJieGardenTargetFound");
target_info["details"]["target_subtype"] = subtype2name(bosky_map.get_target_subtype());
callback(AsstMsg::SubTaskExtraInfo, target_info);
// 完成任务,退出
m_control_ptr->exit_then_stop();
m_task_ptr->set_enable(false);
return std::nullopt;
}
}
}
const auto click_option_task_name = [&](int item, int total) {
if (item > total) {
Log.warn("Event:", event.name, "Total:", total, "Choice", item, "out of range, switch to choice", total);

View File

@@ -224,4 +224,35 @@ public enum RoguelikeMode
/// 深入调查,尽可能稳定地打更多层数,不期而遇采用激进策略
/// </summary>
Exploration = 7,
/// <summary>
/// 20001 - 刷常乐节点,第一层进洞,找不到需要的节点就重开
/// </summary>
FindPlaytime = 20001,
}
/// <summary>
/// 界园常乐节点子类型
/// </summary>
public enum RoguelikeBoskySubNodeType
{
/// <summary>
/// 未知
/// </summary>
Unknown = 0,
/// <summary>
/// 令 - 常乐 掷地有声
/// </summary>
Ling = 1,
/// <summary>
/// 黍 - 常乐 种因得果
/// </summary>
Shu = 2,
/// <summary>
/// 年 - 常乐 三缺一
/// </summary>
Nian = 3,
}

View File

@@ -165,6 +165,7 @@ public static class ConfigurationKeys
public const string RoguelikeMonthlySquadAutoIterate = "Roguelike.MonthlySquadAutoIterate";
public const string RoguelikeMonthlySquadCheckComms = "Roguelike.MonthlySquadCheckComms";
public const string RoguelikeDeepExplorationAutoIterate = "Roguelike.DeepExplorationAutoIterate";
public const string RoguelikeFindPlaytimeTarget = "Roguelike.FindPlaytimeTarget";
public const string ReclamationTheme = "Reclamation.Theme";
public const string ReclamationMode = "Reclamation.Mode";
public const string ReclamationToolToCraft = "Reclamation.ToolToCraft";

View File

@@ -189,6 +189,38 @@ public static class UiLogColor
/// </summary>
public const string ExplorationAbandonedIS = "ExplorationAbandonedIS";
// Bosky Passage Node Colors (JieGarden Theme)
/// <summary>
/// The recommended color for Omissions node (拾遗).
/// </summary>
public const string BoskyOmissionsIS = "BoskyOmissionsIS";
/// <summary>
/// The recommended color for Legend node (传说).
/// </summary>
public const string BoskyLegendIS = "BoskyLegendIS";
/// <summary>
/// The recommended color for OldShop node (故肆).
/// </summary>
public const string BoskyOldShopIS = "BoskyOldShopIS";
/// <summary>
/// The recommended color for Scheme node (筹谋).
/// </summary>
public const string BoskySchemeIS = "BoskySchemeIS";
/// <summary>
/// The recommended color for Playtime node (常乐).
/// </summary>
public const string BoskyPlaytimeIS = "BoskyPlaytimeIS";
/// <summary>
/// The recommended color for Doubts node (杂疑).
/// </summary>
public const string BoskyDoubtsIS = "BoskyDoubtsIS";
// 颜色在MaaWpfGui\Res\Themes中定义
// Brush are defined in MaaWpfGui\Res\Themes
}

View File

@@ -179,6 +179,11 @@ public class AsstRoguelikeTask : AsstBaseTask
/// </summary>
public bool DeepExplorationAutoIterate { get; set; }
/// <summary>
/// Gets or sets FindPlaytime 模式的目标常乐节点子类型
/// </summary>
public RoguelikeBoskySubNodeType FindPlaytimeTarget { get; set; } = RoguelikeBoskySubNodeType.Ling;
/// <summary>
/// Gets or sets a value indicating whether 是否在五层BOSS前停下来
/// </summary>
@@ -253,6 +258,11 @@ public class AsstRoguelikeTask : AsstBaseTask
taskParams["deep_exploration_auto_iterate"] = DeepExplorationAutoIterate;
}
if (Mode == RoguelikeMode.FindPlaytime)
{
taskParams["find_playTime_target"] = (int)FindPlaytimeTarget;
}
if (SamiFirstFloorFoldartal && SamiStartFloorFoldartal.Length > 0)
{
taskParams["first_floor_foldartal"] = SamiStartFloorFoldartal;

View File

@@ -166,6 +166,12 @@ To customize rotation schedules, please use 「{key=InfrastModeCustom}」.</syst
<system:String x:Key="RoguelikeCollectibleModeSquad">Squad to get collectibles</system:String>
<system:String x:Key="RoguelikeStrategyMonthlySquad">Grind Monthly Squad, clear as far as possible</system:String>
<system:String x:Key="RoguelikeStrategyDeepExploration">Grind Deep Investigation, clear as far as possible</system:String>
<system:String x:Key="RoguelikeStrategyFindPlaytime">刷常乐节点,第一层进洞,找不到需要的节点就重开</system:String>
<system:String x:Key="RoguelikeFindPlaytimeTarget">目标常乐节点</system:String>
<system:String x:Key="RoguelikePlaytimeLing">令 - 掷地有声</system:String>
<system:String x:Key="RoguelikePlaytimeShu">黍 - 种因得果</system:String>
<system:String x:Key="RoguelikePlaytimeNian">年 - 三缺一</system:String>
<system:String x:Key="RoguelikeJieGardenTargetFound">发现目标常乐节点 {0}</system:String>
<system:String x:Key="StartingSquad">Starting Squad</system:String>
<system:String x:Key="StartingRoles">Starting Roles</system:String>
<system:String x:Key="StartingCoreChar">Starting {key=Operator}</system:String>
@@ -997,6 +1003,14 @@ Right-click to clear inactive tasks</system:String>
<system:String x:Key="CombatDps">Node: Combat Operation</system:String>
<system:String x:Key="EmergencyDps">Node: Emergency Operation</system:String>
<system:String x:Key="DreadfulFoe">Node: Dreadful Foe</system:String>
<system:String x:Key="BoskyOmissions">节点: 拾遗</system:String>
<system:String x:Key="BoskyLegend">节点: 传说</system:String>
<system:String x:Key="BoskyOldShop">节点: 故肆</system:String>
<system:String x:Key="BoskyYiTrader">节点: 易与</system:String>
<system:String x:Key="BoskyScheme">节点: 筹谋</system:String>
<system:String x:Key="BoskyPlaytime">节点: 常乐</system:String>
<system:String x:Key="BoskyDoubts">节点: 杂疑</system:String>
<system:String x:Key="BoskyDisaster">节点: 灾祸</system:String>
<system:String x:Key="RoguelikeEvent">Event:&#160;</system:String>
<system:String x:Key="EncounterOcrError">Event Name OCR Failed</system:String>
<system:String x:Key="FoldartalGainOcrNextLevel">Foldartal Foresee:&#160;</system:String>

View File

@@ -166,6 +166,12 @@
<system:String x:Key="RoguelikeCollectibleModeSquad">支援選択を得るモードの分隊</system:String>
<system:String x:Key="RoguelikeStrategyMonthlySquad">月間小隊をグラインドし、できるだけ多くの層をクリア</system:String>
<system:String x:Key="RoguelikeStrategyDeepExploration">深層探索をグラインドし、できるだけ多くの層をクリア</system:String>
<system:String x:Key="RoguelikeStrategyFindPlaytime">刷常乐节点,第一层进洞,找不到需要的节点就重开</system:String>
<system:String x:Key="RoguelikeFindPlaytimeTarget">目标常乐节点</system:String>
<system:String x:Key="RoguelikePlaytimeLing">令 - 掷地有声</system:String>
<system:String x:Key="RoguelikePlaytimeShu">黍 - 种因得果</system:String>
<system:String x:Key="RoguelikePlaytimeNian">年 - 三缺一</system:String>
<system:String x:Key="RoguelikeJieGardenTargetFound">发现目标常乐节点 {0}</system:String>
<system:String x:Key="StartingSquad">最初の分隊</system:String>
<system:String x:Key="StartingRoles">最初の職業</system:String>
<system:String x:Key="StartingCoreChar">最初の{key=Operator}</system:String>
@@ -997,6 +1003,14 @@ C:\\leidian\\LDPlayer9
<system:String x:Key="CombatDps">ステージ: 通常作戦</system:String>
<system:String x:Key="EmergencyDps">ステージ: 緊急作戦</system:String>
<system:String x:Key="DreadfulFoe">ステージ: 悪路凶敵</system:String>
<system:String x:Key="BoskyOmissions">节点: 拾遗</system:String>
<system:String x:Key="BoskyLegend">节点: 传说</system:String>
<system:String x:Key="BoskyOldShop">节点: 故肆</system:String>
<system:String x:Key="BoskyYiTrader">节点: 易与</system:String>
<system:String x:Key="BoskyScheme">节点: 筹谋</system:String>
<system:String x:Key="BoskyPlaytime">节点: 常乐</system:String>
<system:String x:Key="BoskyDoubts">节点: 杂疑</system:String>
<system:String x:Key="BoskyDisaster">节点: 灾祸</system:String>
<system:String x:Key="RoguelikeEvent">イベント:</system:String>
<system:String x:Key="EncounterOcrError">イベント名認識失敗</system:String>
<system:String x:Key="FoldartalGainOcrNextLevel">予見啓示板:</system:String>

View File

@@ -166,6 +166,12 @@
<system:String x:Key="RoguelikeCollectibleModeSquad">전기주전자 획득 분대</system:String>
<system:String x:Key="RoguelikeStrategyMonthlySquad">월간 분대 파밍, 최대한 많은 구역 클리어</system:String>
<system:String x:Key="RoguelikeStrategyDeepExploration">심층 조사 파밍, 최대한 많은 구역 클리어</system:String>
<system:String x:Key="RoguelikeStrategyFindPlaytime">刷常乐节点,第一层进洞,找不到需要的节点就重开</system:String>
<system:String x:Key="RoguelikeFindPlaytimeTarget">目标常乐节点</system:String>
<system:String x:Key="RoguelikePlaytimeLing">令 - 掷地有声</system:String>
<system:String x:Key="RoguelikePlaytimeShu">黍 - 种因得果</system:String>
<system:String x:Key="RoguelikePlaytimeNian">年 - 三缺一</system:String>
<system:String x:Key="RoguelikeJieGardenTargetFound">发现目标常乐节点 {0}</system:String>
<system:String x:Key="StartingSquad">시작 분대</system:String>
<system:String x:Key="StartingRoles">모집 조합</system:String>
<system:String x:Key="StartingCoreChar">시작 {key=Operator}</system:String>
@@ -997,6 +1003,14 @@ C:\\leidian\\LDPlayer9
<system:String x:Key="CombatDps">노드: 작전</system:String>
<system:String x:Key="EmergencyDps">노드: 긴급 작전</system:String>
<system:String x:Key="DreadfulFoe">노드: 험난한 길</system:String>
<system:String x:Key="BoskyOmissions">节点: 拾遗</system:String>
<system:String x:Key="BoskyLegend">节点: 传说</system:String>
<system:String x:Key="BoskyOldShop">节点: 故肆</system:String>
<system:String x:Key="BoskyYiTrader">节点: 易与</system:String>
<system:String x:Key="BoskyScheme">节点: 筹谋</system:String>
<system:String x:Key="BoskyPlaytime">节点: 常乐</system:String>
<system:String x:Key="BoskyDoubts">节点: 杂疑</system:String>
<system:String x:Key="BoskyDisaster">节点: 灾祸</system:String>
<system:String x:Key="RoguelikeEvent">이벤트:</system:String>
<system:String x:Key="EncounterOcrError">이벤트 인식 오류</system:String>
<system:String x:Key="FoldartalGainOcrNextLevel">암호판:</system:String>

View File

@@ -166,6 +166,12 @@
<system:String x:Key="RoguelikeCollectibleModeSquad">烧水使用分队</system:String>
<system:String x:Key="RoguelikeStrategyMonthlySquad">刷月度小队,尽可能稳定地打更多层数</system:String>
<system:String x:Key="RoguelikeStrategyDeepExploration">刷深入调查,尽可能稳定地打更多层数</system:String>
<system:String x:Key="RoguelikeStrategyFindPlaytime">刷常乐节点,第一层进洞,找不到需要的节点就重开</system:String>
<system:String x:Key="RoguelikeFindPlaytimeTarget">目标常乐节点</system:String>
<system:String x:Key="RoguelikePlaytimeLing">令 - 掷地有声</system:String>
<system:String x:Key="RoguelikePlaytimeShu">黍 - 种因得果</system:String>
<system:String x:Key="RoguelikePlaytimeNian">年 - 三缺一</system:String>
<system:String x:Key="RoguelikeJieGardenTargetFound">发现目标常乐节点 {0}</system:String>
<system:String x:Key="StartingSquad">开局分队</system:String>
<system:String x:Key="StartingRoles">开局职业组</system:String>
<system:String x:Key="StartingCoreChar">开局{key=Operator}</system:String>
@@ -997,6 +1003,14 @@ C:\\leidian\\LDPlayer9。\n
<system:String x:Key="CombatDps">关卡: 普通作战</system:String>
<system:String x:Key="EmergencyDps">关卡: 紧急作战</system:String>
<system:String x:Key="DreadfulFoe">关卡: 险路恶敌</system:String>
<system:String x:Key="BoskyOmissions">节点: 拾遗</system:String>
<system:String x:Key="BoskyLegend">节点: 传说</system:String>
<system:String x:Key="BoskyOldShop">节点: 故肆</system:String>
<system:String x:Key="BoskyYiTrader">节点: 易与</system:String>
<system:String x:Key="BoskyScheme">节点: 筹谋</system:String>
<system:String x:Key="BoskyPlaytime">节点: 常乐</system:String>
<system:String x:Key="BoskyDoubts">节点: 杂疑</system:String>
<system:String x:Key="BoskyDisaster">节点: 灾祸</system:String>
<system:String x:Key="RoguelikeEvent">事件:</system:String>
<system:String x:Key="EncounterOcrError">事件名识别错误</system:String>
<system:String x:Key="FoldartalGainOcrNextLevel">预见:</system:String>

View File

@@ -165,6 +165,12 @@
<system:String x:Key="RoguelikeCollectibleModeSquad">燒水分隊</system:String>
<system:String x:Key="RoguelikeStrategyMonthlySquad">刷月度小隊,盡可能穩定地打更多層數</system:String>
<system:String x:Key="RoguelikeStrategyDeepExploration">刷深入調查,盡可能穩定地打更多層數</system:String>
<system:String x:Key="RoguelikeStrategyFindPlaytime">刷常乐节点,第一层进洞,找不到需要的节点就重开</system:String>
<system:String x:Key="RoguelikeFindPlaytimeTarget">目标常乐节点</system:String>
<system:String x:Key="RoguelikePlaytimeLing">令 - 掷地有声</system:String>
<system:String x:Key="RoguelikePlaytimeShu">黍 - 种因得果</system:String>
<system:String x:Key="RoguelikePlaytimeNian">年 - 三缺一</system:String>
<system:String x:Key="RoguelikeJieGardenTargetFound">发现目标常乐节点 {0}</system:String>
<system:String x:Key="StartingSquad">開局分隊</system:String>
<system:String x:Key="StartingRoles">開局職業組</system:String>
<system:String x:Key="StartingCoreChar">開局{key=Operator}</system:String>
@@ -995,6 +1001,14 @@ C:\\leidian\\LDPlayer9。\n
<system:String x:Key="CombatDps">關卡: 普通作戰</system:String>
<system:String x:Key="EmergencyDps">關卡: 緊急作戰</system:String>
<system:String x:Key="DreadfulFoe">關卡: 險路惡敵</system:String>
<system:String x:Key="BoskyOmissions">節點: 拾遺</system:String>
<system:String x:Key="BoskyLegend">節點: 傳說</system:String>
<system:String x:Key="BoskyOldShop">節點: 故肆</system:String>
<system:String x:Key="BoskyYiTrader">節點: 易與</system:String>
<system:String x:Key="BoskyScheme">節點: 籌謀</system:String>
<system:String x:Key="BoskyPlaytime">節點: 常樂</system:String>
<system:String x:Key="BoskyDoubts">節點: 雜疑</system:String>
<system:String x:Key="BoskyDisaster">節點: 災禍</system:String>
<system:String x:Key="RoguelikeEvent">事件:</system:String>
<system:String x:Key="EncounterOcrError">事件名辨識錯誤</system:String>
<system:String x:Key="FoldartalGainOcrNextLevel">預見:</system:String>

View File

@@ -117,4 +117,19 @@
<SolidColorBrush x:Key="BossIS" Color="#ab8e8e" />
<SolidColorBrush x:Key="ExplorationAbandonedIS" Color="#9ea1a4" />
<!-- UiLogColor - Bosky Passage Nodes (JieGarden) -->
<SolidColorBrush x:Key="BoskyOmissionsISRaw" Color="#98D8C8" />
<SolidColorBrush x:Key="BoskyLegendISRaw" Color="#00CED1" />
<SolidColorBrush x:Key="BoskyOldShopISRaw" Color="#FFB347" />
<SolidColorBrush x:Key="BoskySchemeISRaw" Color="#9370DB" />
<SolidColorBrush x:Key="BoskyPlaytimeISRaw" Color="#FFB6C1" />
<SolidColorBrush x:Key="BoskyDoubtsISRaw" Color="#F0E68C" />
<SolidColorBrush x:Key="BoskyOmissionsIS" Color="#9ab8b0" />
<SolidColorBrush x:Key="BoskyLegendIS" Color="#8db5b6" />
<SolidColorBrush x:Key="BoskyOldShopIS" Color="#baaa91" />
<SolidColorBrush x:Key="BoskySchemeIS" Color="#a79bb5" />
<SolidColorBrush x:Key="BoskyPlaytimeIS" Color="#baabb0" />
<SolidColorBrush x:Key="BoskyDoubtsIS" Color="#b5b09a" />
</ResourceDictionary>

View File

@@ -99,4 +99,19 @@
<SolidColorBrush x:Key="BossIS" Color="#8a6d6d" />
<SolidColorBrush x:Key="ExplorationAbandonedIS" Color="#7d8083" />
<!-- UiLogColor - Bosky Passage Nodes (JieGarden) -->
<SolidColorBrush x:Key="BoskyOmissionsISRaw" Color="#98D8C8" />
<SolidColorBrush x:Key="BoskyLegendISRaw" Color="#00CED1" />
<SolidColorBrush x:Key="BoskyOldShopISRaw" Color="#FFB347" />
<SolidColorBrush x:Key="BoskySchemeISRaw" Color="#9370DB" />
<SolidColorBrush x:Key="BoskyPlaytimeISRaw" Color="#FFB6C1" />
<SolidColorBrush x:Key="BoskyDoubtsISRaw" Color="#F0E68C" />
<SolidColorBrush x:Key="BoskyOmissionsIS" Color="#79978f" />
<SolidColorBrush x:Key="BoskyLegendIS" Color="#6c9495" />
<SolidColorBrush x:Key="BoskyOldShopIS" Color="#998970" />
<SolidColorBrush x:Key="BoskySchemeIS" Color="#867a94" />
<SolidColorBrush x:Key="BoskyPlaytimeIS" Color="#998a8f" />
<SolidColorBrush x:Key="BoskyDoubtsIS" Color="#948f79" />
</ResourceDictionary>

View File

@@ -28,9 +28,11 @@ using MaaWpfGui.ViewModels.UI;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Mode = MaaWpfGui.Configuration.Single.MaaTask.RoguelikeMode;
using RoguelikeBoskySubNodeType = MaaWpfGui.Configuration.Single.MaaTask.RoguelikeBoskySubNodeType;
using Theme = MaaWpfGui.Configuration.Single.MaaTask.RoguelikeTheme;
namespace MaaWpfGui.ViewModels.UserControl.TaskQueue;
public class RoguelikeSettingsUserControlModel : TaskViewModel
{
static RoguelikeSettingsUserControlModel()
@@ -111,6 +113,7 @@ public class RoguelikeSettingsUserControlModel : TaskViewModel
new() { Display = LocalizationHelper.GetString("RoguelikeStrategyExp"), Value = Mode.Exp },
new() { Display = LocalizationHelper.GetString("RoguelikeStrategyGold"), Value = Mode.Investment },
new() { Display = LocalizationHelper.GetString("RoguelikeStrategyLastReward"), Value = Mode.Collectible },
new() { Display = LocalizationHelper.GetString("RoguelikeStrategyFindPlaytime"), Value = Mode.FindPlaytime },
];
break;
@@ -918,6 +921,36 @@ public class RoguelikeSettingsUserControlModel : TaskViewModel
}
}
private RoguelikeBoskySubNodeType _roguelikeFindPlaytimeTarget = ConfigurationHelper.GetValue(ConfigurationKeys.RoguelikeFindPlaytimeTarget, RoguelikeBoskySubNodeType.Ling);
/// <summary>
/// Gets or sets the target playtime subnode type for FindPlaytime mode.
/// </summary>
public RoguelikeBoskySubNodeType RoguelikeFindPlaytimeTarget
{
get => _roguelikeFindPlaytimeTarget;
set
{
SetAndNotify(ref _roguelikeFindPlaytimeTarget, value);
ConfigurationHelper.SetValue(ConfigurationKeys.RoguelikeFindPlaytimeTarget, value.ToString());
}
}
/// <summary>
/// Gets the list of available playtime target options for FindPlaytime mode.
/// </summary>
public ObservableCollection<GenericCombinedData<RoguelikeBoskySubNodeType>> RoguelikeFindPlaytimeTargetList { get; } = new()
{
new() { Display = LocalizationHelper.GetString("RoguelikePlaytimeLing"), Value = RoguelikeBoskySubNodeType.Ling },
new() { Display = LocalizationHelper.GetString("RoguelikePlaytimeShu"), Value = RoguelikeBoskySubNodeType.Shu },
new() { Display = LocalizationHelper.GetString("RoguelikePlaytimeNian"), Value = RoguelikeBoskySubNodeType.Nian },
};
/// <summary>
/// Gets a value indicating whether the FindPlaytime target selection should be visible.
/// </summary>
public bool RoguelikeFindPlaytimeTargetVisible => RoguelikeMode == Mode.FindPlaytime && RoguelikeTheme == Theme.JieGarden;
private bool _roguelikeStopAtMaxLevel = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.RoguelikeStopAtMaxLevel, bool.FalseString));
/// <summary>
@@ -1057,10 +1090,44 @@ public class RoguelikeSettingsUserControlModel : TaskViewModel
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("RoguelikeEvent") + $" {subTaskDetails!["name"]}", UiLogColor.EventIS);
break;
case "BoskyPassageNode":
{
var nodeType = subTaskDetails!["node_type"]?.ToString();
var (localizedNodeType, logColor) = nodeType switch
{
"Omissions" => (LocalizationHelper.GetString("BoskyOmissions"), UiLogColor.BoskyOmissionsIS),
"Legend" => (LocalizationHelper.GetString("BoskyLegend"), UiLogColor.BoskyLegendIS),
"OldShop" => (LocalizationHelper.GetString("BoskyOldShop"), UiLogColor.BoskyOldShopIS),
"YiTrader" => (LocalizationHelper.GetString("BoskyYiTrader"), UiLogColor.TraderIS),
"Scheme" => (LocalizationHelper.GetString("BoskyScheme"), UiLogColor.BoskySchemeIS),
"Playtime" => (LocalizationHelper.GetString("BoskyPlaytime"), UiLogColor.BoskyPlaytimeIS),
"Doubts" => (LocalizationHelper.GetString("BoskyDoubts"), UiLogColor.BoskyDoubtsIS),
"Disaster" => (LocalizationHelper.GetString("BoskyDisaster"), UiLogColor.EmergencyIS),
_ => (nodeType ?? "Unknown", UiLogColor.EventIS),
};
Instances.TaskQueueViewModel.AddLog(localizedNodeType, logColor);
break;
}
case "EncounterOcrError":
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("EncounterOcrError"), UiLogColor.Error);
break;
case "RoguelikeJieGardenTargetFound":
{
var targetSubtype = subTaskDetails!["target_subtype"]?.ToString();
var localizedTarget = targetSubtype switch
{
"Ling" => LocalizationHelper.GetString("RoguelikePlaytimeLing"),
"Shu" => LocalizationHelper.GetString("RoguelikePlaytimeShu"),
"Nian" => LocalizationHelper.GetString("RoguelikePlaytimeNian"),
_ => targetSubtype ?? "Unknown",
};
Instances.TaskQueueViewModel.AddLog(string.Format(LocalizationHelper.GetString("RoguelikeJieGardenTargetFound"), localizedTarget), UiLogColor.Success);
break;
}
case "FoldartalGainOcrNextLevel":
Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetString("FoldartalGainOcrNextLevel") + $" {subTaskDetails!["foldartal"]}");
break;
@@ -1139,6 +1206,9 @@ public class RoguelikeSettingsUserControlModel : TaskViewModel
// 深入探索
DeepExplorationAutoIterate = RoguelikeDeepExplorationAutoIterate,
// 刷常乐节点
FindPlaytimeTarget = RoguelikeFindPlaytimeTarget,
SamiFirstFloorFoldartal = RoguelikeTheme == Theme.Sami && RoguelikeMode == Mode.Collectible && Roguelike3FirstFloorFoldartal,
SamiStartFloorFoldartal = Roguelike3FirstFloorFoldartals,
SamiNewSquad2StartingFoldartal = Roguelike3NewSquad2StartingFoldartal && RoguelikeSquadIsFoldartal,

View File

@@ -80,6 +80,17 @@
ItemsSource="{Binding RoguelikeRolesList}"
SelectedValue="{Binding RoguelikeRoles}"
SelectedValuePath="Value" />
<hc:ComboBox
Margin="0,5"
d:Visibility="Visible"
hc:InfoElement.Title="{DynamicResource RoguelikeFindPlaytimeTarget}"
DisplayMemberPath="Display"
IsHitTestVisible="{Binding Idle, Source={x:Static setting:TaskQueueViewModel.TaskSettingDataContext}}"
ItemsSource="{Binding RoguelikeFindPlaytimeTargetList}"
SelectedValue="{Binding RoguelikeFindPlaytimeTarget}"
SelectedValuePath="Value"
Visibility="{c:Binding 'RoguelikeMode == task:RoguelikeMode.FindPlaytime and RoguelikeTheme == task:RoguelikeTheme.JieGarden',
Source={x:Static ui:RoguelikeSettingsUserControlModel.Instance}}" />
<StackPanel Margin="0,5">
<StackPanel Margin="0,0,0,5" Orientation="Horizontal">
<controls:TextBlock Margin="8,0,0,0" Text="{DynamicResource StartingCoreChar}" />