feat: 加入肉鸽节点地图识别功能,并应用于萨卡兹肉鸽蓝图快速过图 (#9812)
* feat: 添加 RoguelikeMode::FastPass,负责用蓝图分队快速过图 * chore: update templates for RoguelikeMap and RoguelikeRouting * feat: RoguelikeMap * feat: RoguelikeMapConfig * feat: BrightPointAnalyzer * feat: RoguelikeRouting * chore: Auto update by pre-commit hooks [skip changelog] * fix: 修复会卡在商店门口的bug * fix: adapt new plugin-registration signature * chore: try fix submodule * perf: 复用task * chore: Auto update by pre-commit hooks [skip changelog] * fix: 为函数申明补上定义 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: status102 <102887808+status102@users.noreply.github.com>
21
resource/roguelike/Sarkaz/map.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"theme": "Sarkaz",
|
||||
"node": [
|
||||
{
|
||||
"type": "CombatOps",
|
||||
"template": ["Sarkaz@Roguelike@MapNodeCombatOps.png", "Sarkaz@Roguelike@MapNodeCombatOpsGrey.png"]
|
||||
},
|
||||
{
|
||||
"type": "EmergencyOps",
|
||||
"template": ["Sarkaz@Roguelike@MapNodeEmergencyOps.png", "Sarkaz@Roguelike@MapNodeEmergencyOpsGrey.png"]
|
||||
},
|
||||
{
|
||||
"type": "Encounter",
|
||||
"template": ["Sarkaz@Roguelike@MapNodeEncounter.png", "Sarkaz@Roguelike@MapNodeEncounterGrey.png"]
|
||||
},
|
||||
{
|
||||
"type": "RogueTrader",
|
||||
"template": ["Sarkaz@Roguelike@MapNodeRogueTraderGrey.png"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6982,6 +6982,9 @@
|
||||
"Roguelike@StrategyChange_mode5": {
|
||||
"baseTask": "Roguelike@StrategyChange_default"
|
||||
},
|
||||
"Roguelike@StrategyChange_mode10001": {
|
||||
"baseTask": "Roguelike@StrategyChange_default"
|
||||
},
|
||||
"Roguelike@TempRecruitFlag": {
|
||||
"roi": [587, 76, 100, 20],
|
||||
"templThreshold": 0.5
|
||||
@@ -8979,6 +8982,80 @@
|
||||
"Sami@RoguelikeSettlementConfirm": {
|
||||
"template": "RoguelikeSettlementConfirm.png"
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingConfig": {
|
||||
"doc": [
|
||||
"————————————————————————————————————————————————————————————————",
|
||||
"RoguelikeRoutingTaskPlugin用参数",
|
||||
"————————————————————————————————————————————————————————————————",
|
||||
"(roi.x, roi.y): 生成地图时向左滑动起点",
|
||||
"(roi.width, roi.height): 生成地图时向左滑动终点",
|
||||
"specialParams[0]: 第一列节点的默认横坐标 (Rect.x)",
|
||||
"specialParams[1]: 中间列节点的默认横坐标 (Rect.x)",
|
||||
"specialParams[2]: 最后列节点的默认横坐标 (Rect.x)",
|
||||
"specialParams[3]: 节点 Rect.width",
|
||||
"specialParams[4]: 节点 Rect.height",
|
||||
"specialParams[5]: 两列节点之间的距离",
|
||||
"specialParams[6]: 节点 Rect 下边缘到节点铭牌下边缘的距离",
|
||||
"specialParams[7]: roi 时的 margin offset",
|
||||
"specialParams[8]: 节点间连线方向判定的阈值"
|
||||
],
|
||||
"template": "empty.png",
|
||||
"roi": [1000, 360, 570, 360],
|
||||
"specialParams": [321, 341, 586, 138, 47, 450, 32, 15, 7]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingNodeAnalyze": {
|
||||
"template": [
|
||||
"Sarkaz@Roguelike@MapNodeCombatOps.png",
|
||||
"Sarkaz@Roguelike@MapNodeCombatOpsGrey.png",
|
||||
"Sarkaz@Roguelike@MapNodeEmergencyOps.png",
|
||||
"Sarkaz@Roguelike@MapNodeEmergencyOpsGrey.png",
|
||||
"Sarkaz@Roguelike@MapNodeEncounter.png",
|
||||
"Sarkaz@Roguelike@MapNodeEncounterGrey.png",
|
||||
"Sarkaz@Roguelike@MapNodeRogueTraderGrey.png"
|
||||
],
|
||||
"roi": [246, 60, 288, 575],
|
||||
"postDelay": 300
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingExitThenContinue": {
|
||||
"template": "Roguelike@ExitThenAbandon.png",
|
||||
"roi": [18, 8, 53, 53],
|
||||
"action": "ClickSelf",
|
||||
"next": ["Sarkaz@Roguelike@RoutingContinue"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingContinue": {
|
||||
"template": "Sarkaz@Roguelike@Continue.png",
|
||||
"roi": [1050, 550, 210, 100],
|
||||
"action": "ClickSelf",
|
||||
"next": ["Sarkaz@Roguelike@RoutingContinueConfirm"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingContinueConfirm": {
|
||||
"Doc": "next 里的 RoutingNodeAnalyze 是为了确保回到地图",
|
||||
"algorithm": "JustReturn",
|
||||
"action": "ClickRect",
|
||||
"specificRect": [50, 360, 0, 0],
|
||||
"next": ["Sarkaz@Roguelike@RoutingNodeAnalyze", "Sarkaz@Roguelike@RoutingContinueConfirm"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingRefreshNode": {
|
||||
"roi": [796, 150, 50, 150],
|
||||
"action": "ClickSelf",
|
||||
"next": ["Sarkaz@Roguelike@RoutingRefreshNodeConfirm"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingRefreshNodeConfirm": {
|
||||
"algorithm": "OcrDetect",
|
||||
"roi": [993, 472, 53, 26],
|
||||
"text": ["确定"],
|
||||
"action": "ClickSelf",
|
||||
"postDelay": 300,
|
||||
"next": ["Sarkaz@Roguelike@RoutingRefreshNodeWaiting", "Sarkaz@Roguelike@RoutingRefreshNodeCompleted"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingRefreshNodeWaiting": {
|
||||
"baseTask": "LoadingText",
|
||||
"postDelay": 300,
|
||||
"next": ["Sarkaz@Roguelike@RoutingRefreshNodeWaiting", "Sarkaz@Roguelike@RoutingRefreshNodeCompleted"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingRefreshNodeCompleted": {
|
||||
"algorithm": "JustReturn"
|
||||
},
|
||||
"Sarkaz@Roguelike@Abandon": {
|
||||
"crop_doc": {
|
||||
"roi": [1067, 255, 159, 154],
|
||||
@@ -9907,6 +9984,55 @@
|
||||
"Sarkaz@Roguelike@StageDreadfulFoe-5"
|
||||
]
|
||||
},
|
||||
"Sarkaz@Roguelike@Stages_routing": {
|
||||
"algorithm": "JustReturn",
|
||||
"next": [
|
||||
"Sarkaz@Roguelike@NextLevel",
|
||||
"Sarkaz@Roguelike@CloseCollectionContinue",
|
||||
"Sarkaz@Roguelike@CloseCollectionClose",
|
||||
"Sarkaz@Roguelike@ChooseBurdenOperatorAtStart",
|
||||
"Sarkaz@Roguelike@DecreaseBurdenChaos",
|
||||
"Sarkaz@Roguelike@DecreaseBurdenRetardation",
|
||||
"Sarkaz@Roguelike@Routing"
|
||||
]
|
||||
},
|
||||
"Sarkaz@Roguelike@Routing": {
|
||||
"template": [
|
||||
"Sarkaz@Roguelike@StageCombatDps",
|
||||
"Sarkaz@Roguelike@StageEmergencyDps",
|
||||
"Sarkaz@Roguelike@StageDreadfulFoe",
|
||||
"Sarkaz@Roguelike@StageDreadfulFoe-5",
|
||||
"Sarkaz@Roguelike@StageEncounter",
|
||||
"Sarkaz@Roguelike@StageBoons",
|
||||
"Sarkaz@Roguelike@StageSafeHouse",
|
||||
"Sarkaz@Roguelike@StageSafeHouseCR",
|
||||
"Sarkaz@Roguelike@StageSafeHouseHD",
|
||||
"Sarkaz@Roguelike@StageGambling",
|
||||
"Sarkaz@Roguelike@StageTrader",
|
||||
"Sarkaz@Roguelike@StageTraderCR",
|
||||
"Sarkaz@Roguelike@StageTraderHD",
|
||||
"Sarkaz@Roguelike@StageWindAndRain",
|
||||
"Sarkaz@Roguelike@StageEmergencyTransportation",
|
||||
"Sarkaz@Roguelike@StageBoskyPassage",
|
||||
"Sarkaz@Roguelike@StageProphecy",
|
||||
"Sarkaz@Roguelike@StageFilterTruth",
|
||||
"Sarkaz@Roguelike@StageFilterTruthCR",
|
||||
"Sarkaz@Roguelike@StageFilterTruthHD",
|
||||
"Sarkaz@Roguelike@StageConfrontation"
|
||||
],
|
||||
"next": ["Sarkaz@Roguelike@RoutingAction"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingAction": {
|
||||
"algorithm": "JustReturn"
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingActionEncounter": {
|
||||
"algorithm": "JustReturn",
|
||||
"next": ["Sarkaz@Roguelike@StageEncounterEnter"]
|
||||
},
|
||||
"Sarkaz@Roguelike@RoutingActionRogueTrader": {
|
||||
"algorithm": "JustReturn",
|
||||
"next": ["Sarkaz@Roguelike@StageTraderEnter"]
|
||||
},
|
||||
"Sarkaz@Roguelike@StartExplore": {
|
||||
"roi": [1014, 481, 266, 219]
|
||||
},
|
||||
@@ -9951,6 +10077,18 @@
|
||||
["诡谲断章", "_exit"]
|
||||
]
|
||||
},
|
||||
"Sarkaz@Roguelike@StrategyChange_mode10001": {
|
||||
"text": ["_routing", "_exit"],
|
||||
"ocrReplace": [
|
||||
["熔魂之始", "_routing"],
|
||||
["锻铁根须", "_exit"],
|
||||
["灰铸迷城", "_exit"],
|
||||
["或然歧域", "_exit"],
|
||||
["虚实疆界", "_exit"],
|
||||
["辉光天顶", "_exit"],
|
||||
["诡谲断章", "_exit"]
|
||||
]
|
||||
},
|
||||
"Sarkaz@Roguelike@TodoEnter": {},
|
||||
"Sarkaz@Roguelike@WaitForStartButtonClicked": {
|
||||
"template": "Roguelike@QuickFormation.png"
|
||||
|
||||
BIN
resource/template/Sarkaz@Roguelike@MapNodeCombatOps.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
resource/template/Sarkaz@Roguelike@MapNodeCombatOpsGrey.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
resource/template/Sarkaz@Roguelike@MapNodeEmergencyOps.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
resource/template/Sarkaz@Roguelike@MapNodeEmergencyOpsGrey.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
resource/template/Sarkaz@Roguelike@MapNodeEncounter.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
resource/template/Sarkaz@Roguelike@MapNodeEncounterGrey.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
resource/template/Sarkaz@Roguelike@MapNodeRogueTraderGrey.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
resource/template/Sarkaz@Roguelike@RoutingRefreshNode.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
@@ -16,6 +16,7 @@
|
||||
#include "Miscellaneous/TilePack.h"
|
||||
#include "OnnxSessions.h"
|
||||
#include "Roguelike/RoguelikeCopilotConfig.h"
|
||||
#include "Roguelike/RoguelikeMapConfig.h"
|
||||
#include "Roguelike/RoguelikeRecruitConfig.h"
|
||||
#include "Roguelike/RoguelikeShoppingConfig.h"
|
||||
#include "Roguelike/RoguelikeStageEncounterConfig.h"
|
||||
@@ -230,6 +231,11 @@ bool asst::ResourceLoader::load(const std::filesystem::path& path)
|
||||
RoguelikeStageEncounterConfig,
|
||||
"roguelike"_p / "Sami"_p / "encounter"_p / "collapse.json"_p);
|
||||
|
||||
// –––––––– Roguelike Map Config ––––––––––––––––––––––––––––––––––––––––––––------
|
||||
LoadResourceAndCheckRet(
|
||||
RoguelikeMapConfig,
|
||||
"roguelike"_p / "Sarkaz"_p / "map.json"_p);
|
||||
|
||||
// –––––––– Sami Plugin Config ––––––––––––––––––––––––––––––––––––––––––––––––––––
|
||||
LoadResourceAndCheckRet(
|
||||
RoguelikeFoldartalConfig,
|
||||
|
||||
47
src/MaaCore/Config/Roguelike/RoguelikeMapConfig.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "RoguelikeMapConfig.h"
|
||||
|
||||
#include <meojson/json.hpp>
|
||||
|
||||
#include "Utils/Logger.hpp"
|
||||
|
||||
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 }
|
||||
};
|
||||
|
||||
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());
|
||||
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);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void asst::RoguelikeMapConfig::clear()
|
||||
{
|
||||
m_templ_type_mappings.clear();
|
||||
}
|
||||
28
src/MaaCore/Config/Roguelike/RoguelikeMapConfig.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Config/AbstractConfig.h"
|
||||
#include "Task/Roguelike/Map/RoguelikeMap.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
inline static auto& RoguelikeMapInfo = RoguelikeMapConfig::get_instance();
|
||||
}
|
||||
@@ -103,6 +103,8 @@
|
||||
<ClInclude Include="Task\Roguelike\Sami\RoguelikeFoldartalStartTaskPlugin.h" />
|
||||
<ClInclude Include="Task\Roguelike\Sami\RoguelikeFoldartalUseTaskPlugin.h" />
|
||||
<ClInclude Include="Task\Roguelike\Sami\RoguelikeCollapsalParadigmTaskPlugin.h" />
|
||||
<ClInclude Include="Task\Roguelike\Map\RoguelikeMap.h" />
|
||||
<ClInclude Include="Task\Roguelike\Map\RoguelikeRoutingTaskPlugin.h" />
|
||||
<ClInclude Include="Task\SSS\SSSBattleProcessTask.h" />
|
||||
<ClInclude Include="Task\SSS\SSSDropRewardsTaskPlugin.h" />
|
||||
<ClInclude Include="Task\SSS\SSSStageManagerTask.h" />
|
||||
@@ -130,6 +132,7 @@
|
||||
<ClInclude Include="Vision\Miscellaneous\RecruitImageAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Miscellaneous\StageDropsImageAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Miscellaneous\OperBoxImageAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Miscellaneous\BrightPointAnalyzer.h" />
|
||||
<ClInclude Include="Vision\MultiMatcher.h" />
|
||||
<ClInclude Include="Vision\OCRer.h" />
|
||||
<ClInclude Include="Vision\TemplDetOCRer.h" />
|
||||
@@ -153,6 +156,7 @@
|
||||
<ClInclude Include="Config\Miscellaneous\TilePack.h" />
|
||||
<ClInclude Include="Config\ResourceLoader.h" />
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeCopilotConfig.h" />
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeMapConfig.h" />
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeRecruitConfig.h" />
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeShoppingConfig.h" />
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeStageEncounterConfig.h" />
|
||||
@@ -281,6 +285,8 @@
|
||||
<ClCompile Include="Task\Roguelike\Sami\RoguelikeFoldartalStartTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\Roguelike\Sami\RoguelikeFoldartalUseTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\Roguelike\Sami\RoguelikeCollapsalParadigmTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\Roguelike\Map\RoguelikeMap.cpp" />
|
||||
<ClCompile Include="Task\Roguelike\Map\RoguelikeRoutingTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\Roguelike\RoguelikeConfig.cpp" />
|
||||
<ClCompile Include="Task\Roguelike\RoguelikeDifficultySelectionTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\Roguelike\RoguelikeInvestTaskPlugin.cpp" />
|
||||
@@ -311,6 +317,7 @@
|
||||
<ClCompile Include="Vision\Miscellaneous\RecruitImageAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Miscellaneous\StageDropsImageAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Miscellaneous\OperBoxImageAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Miscellaneous\BrightPointAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\MultiMatcher.cpp" />
|
||||
<ClCompile Include="Vision\OCRer.cpp" />
|
||||
<ClCompile Include="Vision\TemplDetOCRer.cpp" />
|
||||
@@ -332,6 +339,7 @@
|
||||
<ClCompile Include="Config\Miscellaneous\TilePack.cpp" />
|
||||
<ClCompile Include="Config\ResourceLoader.cpp" />
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeCopilotConfig.cpp" />
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeMapConfig.cpp" />
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeRecruitConfig.cpp" />
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeShoppingConfig.cpp" />
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeStageEncounterConfig.cpp" />
|
||||
|
||||
@@ -225,6 +225,9 @@
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeCopilotConfig.h">
|
||||
<Filter>Source\Resource\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeMapConfig.h">
|
||||
<Filter>Source\Resource\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Config\Roguelike\RoguelikeRecruitConfig.h">
|
||||
<Filter>Source\Resource\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
@@ -456,6 +459,12 @@
|
||||
<ClInclude Include="Task\Roguelike\RoguelikeBattleTaskPlugin.h">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Task\Roguelike\Map\RoguelikeMap.h">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Task\Roguelike\Map\RoguelikeRoutingTask.h">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Task\Roguelike\RoguelikeConfig.h">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClInclude>
|
||||
@@ -639,6 +648,9 @@
|
||||
<ClInclude Include="Vision\Miscellaneous\OperBoxImageAnalyzer.h">
|
||||
<Filter>Source\Vision\Miscellaneous</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Vision\Miscellaneous\BrightPointAnalyzer.h">
|
||||
<Filter>Source\Vision\Miscellaneous</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Vision\Config\MatcherConfig.h">
|
||||
<Filter>Source\Vision\Config</Filter>
|
||||
</ClInclude>
|
||||
@@ -791,6 +803,9 @@
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeCopilotConfig.cpp">
|
||||
<Filter>Source\Resource\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeMapConfig.cpp">
|
||||
<Filter>Source\Resource\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Config\Roguelike\RoguelikeRecruitConfig.cpp">
|
||||
<Filter>Source\Resource\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
@@ -968,6 +983,12 @@
|
||||
<ClCompile Include="Task\Roguelike\RoguelikeBattleTaskPlugin.cpp">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Task\Roguelike\Map\RoguelikeMap.cpp">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Task\Roguelike\Map\RoguelikeRoutingTaskPlugin.cpp">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Task\Roguelike\RoguelikeConfig.cpp">
|
||||
<Filter>Source\Task\Roguelike</Filter>
|
||||
</ClCompile>
|
||||
@@ -1127,6 +1148,9 @@
|
||||
<ClCompile Include="Vision\Miscellaneous\OperBoxImageAnalyzer.cpp">
|
||||
<Filter>Source\Vision\Miscellaneous</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Vision\Miscellaneous\BrightPointAnalyzer.cpp">
|
||||
<Filter>Source\Vision\Miscellaneous</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Vision\Config\MatcherConfig.cpp">
|
||||
<Filter>Source\Vision\Config</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
#include "Task/Roguelike/Sami/RoguelikeFoldartalStartTaskPlugin.h"
|
||||
#include "Task/Roguelike/Sami/RoguelikeFoldartalUseTaskPlugin.h"
|
||||
|
||||
// –––––––– 萨卡兹主题专用配置及插件 –––––––––––––––––––
|
||||
#include "Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h"
|
||||
|
||||
#include "Utils/Logger.hpp"
|
||||
|
||||
asst::RoguelikeTask::RoguelikeTask(const AsstCallback& callback, Assistant* inst) :
|
||||
@@ -82,6 +85,10 @@ asst::RoguelikeTask::RoguelikeTask(const AsstCallback& callback, Assistant* inst
|
||||
m_roguelike_task_ptr->register_plugin<RoguelikeFoldartalStartTaskPlugin>(m_config_ptr, m_control_ptr);
|
||||
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);
|
||||
|
||||
// 这个任务如果卡住会放弃当前的肉鸽并重新开始,所以多添加亿点。先这样凑合用
|
||||
for (int i = 0; i != 999; ++i) {
|
||||
m_subtasks.emplace_back(m_roguelike_task_ptr);
|
||||
|
||||
271
src/MaaCore/Task/Roguelike/Map/RoguelikeMap.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
#include "RoguelikeMap.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/Ranges.hpp"
|
||||
|
||||
asst::RoguelikeMap::RoguelikeMap()
|
||||
{
|
||||
m_curr_pos = create_and_insert_node(RoguelikeNodeType::Init, init_index, 0).value();
|
||||
}
|
||||
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
// update map
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
std::optional<size_t>
|
||||
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);
|
||||
}
|
||||
|
||||
void asst::RoguelikeMap::add_edge(const size_t& source, const size_t& target)
|
||||
{
|
||||
if (source >= m_nodes.size() || target >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
const RoguelikeNodePtr& source_node = m_nodes.at(source);
|
||||
const RoguelikeNodePtr& target_node = m_nodes.at(target);
|
||||
source_node->succs.emplace_back(target);
|
||||
target_node->preds.emplace_back(source);
|
||||
Log.info(__FUNCTION__, "| Node", source, "-> Node", target);
|
||||
}
|
||||
|
||||
void asst::RoguelikeMap::set_curr_pos(const size_t& node_index)
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.info(__FUNCTION__, "| move from Node", m_curr_pos, "to Node ", node_index);
|
||||
m_curr_pos = node_index;
|
||||
}
|
||||
|
||||
void asst::RoguelikeMap::update_node_costs()
|
||||
{
|
||||
for (const RoguelikeNodePtr& node : ranges::reverse_view(m_nodes)) {
|
||||
node->cost = m_cost_fun(node);
|
||||
if (!node->succs.empty()) {
|
||||
auto succ_costs = node->succs | views::transform([&](const size_t node_index) {
|
||||
return m_cost_fun(m_nodes.at(node_index));
|
||||
});
|
||||
node->cost += ranges::min(succ_costs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void asst::RoguelikeMap::reset()
|
||||
{
|
||||
m_nodes.clear();
|
||||
m_column_indices.clear();
|
||||
m_curr_pos = create_and_insert_node(RoguelikeNodeType::Init, init_index, 0).value();
|
||||
}
|
||||
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
// get map information
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
size_t asst::RoguelikeMap::get_column_begin(const size_t& column) const
|
||||
{
|
||||
if (column == init_index) {
|
||||
return init_index;
|
||||
}
|
||||
if (column >= m_column_indices.size()) {
|
||||
Log.warn(__FUNCTION__, "| column does not exist");
|
||||
return m_nodes.size();
|
||||
}
|
||||
return m_column_indices.at(column - 1);
|
||||
}
|
||||
|
||||
size_t asst::RoguelikeMap::get_column_end(const size_t& column) const
|
||||
{
|
||||
if (column >= m_column_indices.size()) {
|
||||
Log.warn(__FUNCTION__, "| column does not exist");
|
||||
return m_nodes.size();
|
||||
}
|
||||
return m_column_indices.at(column);
|
||||
}
|
||||
|
||||
size_t asst::RoguelikeMap::get_next_node() const
|
||||
{
|
||||
const RoguelikeNodePtr curr = m_nodes.at(m_curr_pos);
|
||||
|
||||
if (curr->succs.empty()) {
|
||||
Log.error(__FUNCTION__, "| no successor nodes");
|
||||
return m_curr_pos;
|
||||
}
|
||||
|
||||
const size_t next_index =
|
||||
ranges::min(curr->succs, [&](const size_t& node1_index, const size_t& node2_index) {
|
||||
return m_nodes.at(node1_index)->cost < m_nodes.at(node2_index)->cost;
|
||||
});
|
||||
return next_index;
|
||||
}
|
||||
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
// get node fields
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
/*
|
||||
#define DEFINE_GET_NODE_FIELD(RETURN_TYPE, FIELD) \
|
||||
RETURN_TYPE asst::RoguelikeMap::get_node_##FIELD(const size_t& node_index) const \
|
||||
{ \
|
||||
if (node_index >= m_nodes.size()) { \
|
||||
Log.error(__FUNCTION__, "| node does not exist"); \
|
||||
} \
|
||||
\
|
||||
return m_nodes.at(node_index)->FIELD; \
|
||||
}
|
||||
|
||||
DEFINE_GET_NODE_FIELD(asst::RoguelikeNodeType, type)
|
||||
DEFINE_GET_NODE_FIELD(size_t, column)
|
||||
DEFINE_GET_NODE_FIELD(int, y)
|
||||
DEFINE_GET_NODE_FIELD(bool, visited)
|
||||
DEFINE_GET_NODE_FIELD(std::vector<size_t>, succs)
|
||||
DEFINE_GET_NODE_FIELD(std::vector<size_t>, preds)
|
||||
DEFINE_GET_NODE_FIELD(int, cost)
|
||||
DEFINE_GET_NODE_FIELD(int, refresh_times)
|
||||
|
||||
#undef DEFINE_GET_NODE_FIELD
|
||||
*/
|
||||
|
||||
asst::RoguelikeNodeType asst::RoguelikeMap::get_node_type(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->type;
|
||||
}
|
||||
|
||||
size_t asst::RoguelikeMap::get_node_column(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->column;
|
||||
}
|
||||
|
||||
int asst::RoguelikeMap::get_node_y(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->y;
|
||||
}
|
||||
|
||||
bool asst::RoguelikeMap::get_node_visited(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->visited;
|
||||
}
|
||||
|
||||
std::vector<size_t> asst::RoguelikeMap::get_node_succs(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->succs;
|
||||
}
|
||||
|
||||
std::vector<size_t> asst::RoguelikeMap::get_node_preds(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->preds;
|
||||
}
|
||||
|
||||
int asst::RoguelikeMap::get_node_cost(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->cost;
|
||||
}
|
||||
|
||||
int asst::RoguelikeMap::get_node_refresh_times(const size_t& node_index) const
|
||||
{
|
||||
if (node_index >= m_nodes.size()) {
|
||||
Log.error(__FUNCTION__, "| node does not exist");
|
||||
}
|
||||
|
||||
return m_nodes.at(node_index)->refresh_times;
|
||||
}
|
||||
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
// set node fields
|
||||
// ————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
void asst::RoguelikeMap::set_node_type(const size_t& node_index, RoguelikeNodeType type)
|
||||
{
|
||||
m_nodes.at(node_index)->type = type;
|
||||
}
|
||||
|
||||
void asst::RoguelikeMap::set_node_visited(const size_t& node_index, bool visisted)
|
||||
{
|
||||
m_nodes.at(node_index)->visited = visisted;
|
||||
}
|
||||
|
||||
void asst::RoguelikeMap::set_node_refresh_times(const size_t& node_index, int refresh_times)
|
||||
{
|
||||
m_nodes.at(node_index)->refresh_times = refresh_times;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// private
|
||||
// ================================================================================
|
||||
|
||||
std::optional<size_t> asst::RoguelikeMap::insert_node(const RoguelikeNodePtr& node, const size_t& column)
|
||||
{
|
||||
// 第一个 node 必须为 init node
|
||||
if (column != init_index && m_nodes.empty()) [[unlikely]] {
|
||||
Log.error(__FUNCTION__, "| insert node to column", column, "before init node");
|
||||
return std::nullopt;
|
||||
}
|
||||
// 只允许有一个 init node
|
||||
if (column == init_index && column < m_column_indices.size() && m_column_indices.at(column) > 0)
|
||||
[[unlikely]] {
|
||||
Log.error(__FUNCTION__, "| init node has already exist");
|
||||
return std::nullopt;
|
||||
}
|
||||
// 边界鉴定
|
||||
if (column > std::numeric_limits<int>::max()) [[unlikely]] { // to avoid narrowing conversion
|
||||
Log.error(__FUNCTION__, "| column index", column, "is out of boundary");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 在现有列外插入 node,为其创建新列
|
||||
if (column >= m_column_indices.size()) {
|
||||
m_column_indices.insert(m_column_indices.end(), column - m_column_indices.size() + 1, m_nodes.size());
|
||||
}
|
||||
|
||||
const size_t index = m_column_indices.at(column);
|
||||
|
||||
// 边界鉴定
|
||||
if (index > std::numeric_limits<int>::max()) [[unlikely]] { // to avoid narrowing conversion
|
||||
Log.error(__FUNCTION__, "| node index", index, "is out of boundary");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
m_nodes.insert(m_nodes.begin() + static_cast<int>(index), node);
|
||||
std::for_each(m_column_indices.begin() + static_cast<int>(column), m_column_indices.end(), [](size_t& x) {
|
||||
x += 1;
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
117
src/MaaCore/Task/Roguelike/Map/RoguelikeMap.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace asst
|
||||
{
|
||||
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 // 狭路相逢
|
||||
};
|
||||
|
||||
struct RoguelikeNode
|
||||
{
|
||||
RoguelikeNodeType type = RoguelikeNodeType::Unknown;
|
||||
const size_t column = 0;
|
||||
const int y = 0;
|
||||
bool visited = false;
|
||||
std::vector<size_t> succs; // successors 在 RoguelikeMap().m_nodes 中的 index
|
||||
std::vector<size_t> preds; // predecessors 在 RoguelikeMap().m_nodes 中的 index
|
||||
int cost = 0;
|
||||
// –––––––– 萨卡兹主题引入 ––––––––––––––––––––––––
|
||||
int refresh_times = 0;
|
||||
|
||||
RoguelikeNode(const RoguelikeNodeType _t, const size_t _c, const int _y) :
|
||||
type(_t),
|
||||
column(_c),
|
||||
y(_y)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using RoguelikeNodePtr = std::shared_ptr<RoguelikeNode>;
|
||||
using RoguelikeNodeCostFun = std::function<int(const RoguelikeNodePtr&)>;
|
||||
|
||||
// ================================================================================
|
||||
// Q: RoguelikeMap 应该支持哪些功能?
|
||||
// A: 原则上我希望 RoguelikeNode 和 RoguelikeNodePtr 仅在RoguelikeMap 内部调用
|
||||
// 外部操作对节点的识别以 index 为准, 因此 public 成员函数往往应当接受 size_t 类型
|
||||
// 的 node_index 参数
|
||||
// ================================================================================
|
||||
class RoguelikeMap
|
||||
{
|
||||
public:
|
||||
RoguelikeMap();
|
||||
|
||||
// ———————— update map ————————————————————————————————————————————————————————————
|
||||
// 创建并插入一个 node,返回新 node 的 index
|
||||
std::optional<size_t>
|
||||
create_and_insert_node(const 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);
|
||||
void set_cost_fun(const RoguelikeNodeCostFun& cost_fun) { m_cost_fun = cost_fun; }
|
||||
void update_node_costs();
|
||||
void reset();
|
||||
|
||||
// ———————— get map details ———————————————————————————————————————————————————————
|
||||
size_t size() const { return m_nodes.size(); }
|
||||
size_t get_num_columns() const { return m_column_indices.size(); }
|
||||
size_t get_curr_pos() const { return m_curr_pos; }
|
||||
size_t get_column_begin(const size_t& column) const;
|
||||
size_t get_column_end(const size_t& column) const;
|
||||
size_t get_next_node() const;
|
||||
|
||||
// ———————— get node fields ———————————————————————————————————————————————————————
|
||||
RoguelikeNodeType get_node_type(const size_t& node_index) const;
|
||||
size_t get_node_column(const size_t& node_index) const;
|
||||
int get_node_y(const size_t& node_index) const;
|
||||
bool get_node_visited(const size_t& node_index) const;
|
||||
std::vector<size_t> get_node_succs(const size_t& node_index) const;
|
||||
std::vector<size_t> get_node_preds(const size_t& node_index) const;
|
||||
int get_node_cost(const size_t& node_index) const;
|
||||
int get_node_refresh_times(const size_t& node_index) const;
|
||||
|
||||
// ———————— set node fields ———————————————————————————————————————————————————————
|
||||
void set_node_type(const size_t& node_index, RoguelikeNodeType type);
|
||||
void set_node_visited(const size_t& node_index, bool visisted);
|
||||
void set_node_refresh_times(const size_t& node_index, int refresh_times);
|
||||
|
||||
// ———————— constants and variables ———————————————————————————————————————————————
|
||||
const size_t init_index = 0; // 常量,既是 init 的 node index 也是它的 column index
|
||||
|
||||
private:
|
||||
// ———————— update map ————————————————————————————————————————————————————————————
|
||||
std::optional<size_t> insert_node(const RoguelikeNodePtr& node, const size_t& column);
|
||||
|
||||
// ———————— constants and variables ———————————————————————————————————————————————
|
||||
std::vector<RoguelikeNodePtr> m_nodes;
|
||||
std::vector<size_t> m_column_indices; // m_column_indices[c] 代表列 c 的 node index 的上限 (exclusive)
|
||||
size_t m_curr_pos = 0; // 当前位置的 node index
|
||||
RoguelikeNodeCostFun m_cost_fun = [&]([[maybe_unused]] const RoguelikeNodePtr& node) { return 0; };
|
||||
};
|
||||
}
|
||||
333
src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
#include "RoguelikeRoutingTaskPlugin.h"
|
||||
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
|
||||
#include "Config/TaskData.h"
|
||||
#include "Controller/Controller.h"
|
||||
#include "Task/ProcessTask.h"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/NoWarningCV.h"
|
||||
#include "Vision/Matcher.h"
|
||||
#include "Vision/Miscellaneous/BrightPointAnalyzer.h"
|
||||
#include "Vision/MultiMatcher.h"
|
||||
|
||||
bool asst::RoguelikeRoutingTaskPlugin::load_params([[maybe_unused]] const json::value& params)
|
||||
{
|
||||
LogTraceFunction;
|
||||
const std::string& theme = m_config->get_theme();
|
||||
|
||||
// 本插件暂处于实验阶段,仅用于萨卡兹肉鸽
|
||||
if (theme != RoguelikeTheme::Sarkaz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::shared_ptr<MatchTaskInfo> config = Task.get<MatchTaskInfo>(theme + "@Roguelike@RoutingConfig");
|
||||
|
||||
m_swipe_source.x = config->roi.x;
|
||||
m_swipe_source.y = config->roi.y;
|
||||
m_swipe_target.x = config->roi.width;
|
||||
m_swipe_target.y = config->roi.height;
|
||||
m_origin_x = config->special_params.at(0);
|
||||
m_middle_x = config->special_params.at(1);
|
||||
m_last_x = config->special_params.at(2);
|
||||
m_node_width = config->special_params.at(3);
|
||||
m_node_height = config->special_params.at(4);
|
||||
m_column_offset = config->special_params.at(5);
|
||||
m_nameplate_offset = config->special_params.at(6);
|
||||
m_roi_margin = config->special_params.at(7);
|
||||
m_direction_threshold = config->special_params.at(8);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void asst::RoguelikeRoutingTaskPlugin::reset_in_run_variables()
|
||||
{
|
||||
m_map.reset();
|
||||
m_need_generate_map = true;
|
||||
m_selected_column = 0;
|
||||
m_selected_x = 0;
|
||||
}
|
||||
|
||||
bool asst::RoguelikeRoutingTaskPlugin::verify(const AsstMsg msg, const json::value& details) const
|
||||
{
|
||||
if (msg != AsstMsg::SubTaskStart || details.get("subtask", std::string()) != "ProcessTask") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& task_name = details.get("details", "task", "");
|
||||
if (task_name == m_config->get_theme() + "@Roguelike@Routing") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool asst::RoguelikeRoutingTaskPlugin::_run()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
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();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void asst::RoguelikeRoutingTaskPlugin::generate_map()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
const std::string& theme = m_config->get_theme();
|
||||
|
||||
m_map.reset();
|
||||
size_t curr_col = m_map.init_index + 1;
|
||||
Rect roi = Task.get<MatchTaskInfo>(theme + "@Roguelike@RoutingNodeAnalyze")->roi;
|
||||
|
||||
// 第一列节点
|
||||
cv::Mat image = ctrler()->get_image();
|
||||
MultiMatcher node_analyzer(image);
|
||||
node_analyzer.set_task_info(theme + "@Roguelike@RoutingNodeAnalyze");
|
||||
if (!node_analyzer.analyze()) {
|
||||
Log.error(__FUNCTION__, "| no nodes found in the first column");
|
||||
return;
|
||||
}
|
||||
MultiMatcher::ResultsVec match_results = node_analyzer.get_result();
|
||||
sort_by_vertical_(match_results); // 按照垂直方向排序(从上到下)
|
||||
for (const auto& [rect, score, templ_name] : match_results) {
|
||||
const RoguelikeNodeType type = RoguelikeMapInfo.templ2type(theme, templ_name);
|
||||
const size_t node = m_map.create_and_insert_node(type, curr_col, rect.y).value();
|
||||
generate_edges(node, image, rect.x);
|
||||
}
|
||||
|
||||
// 第二列及以后的节点
|
||||
roi.x += m_column_offset;
|
||||
node_analyzer.set_roi(roi);
|
||||
while (!need_exit() && node_analyzer.analyze()) {
|
||||
++curr_col;
|
||||
match_results = node_analyzer.get_result();
|
||||
sort_by_vertical_(match_results);
|
||||
for (const auto& [rect, score, templ_name] : match_results) {
|
||||
const RoguelikeNodeType type = RoguelikeMapInfo.templ2type(theme, templ_name);
|
||||
const size_t node = m_map.create_and_insert_node(type, curr_col, rect.y).value();
|
||||
generate_edges(node, image, rect.x);
|
||||
}
|
||||
ctrler()->swipe(m_swipe_source, m_swipe_target, 200, true);
|
||||
sleep(200);
|
||||
image = ctrler()->get_image();
|
||||
node_analyzer.set_image(image);
|
||||
}
|
||||
|
||||
ProcessTask(*this, { theme + "@Roguelike@RoutingExitThenContinue" }).run(); // 通过退出重进回到初始位置
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void asst::RoguelikeRoutingTaskPlugin::generate_edges(const size_t& node, const cv::Mat& image, const int& node_x)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
const size_t node_column = m_map.get_node_column(node);
|
||||
|
||||
if (node_column == m_map.init_index) {
|
||||
Log.error(__FUNCTION__, "| cannot generate edges for init node");
|
||||
return;
|
||||
}
|
||||
|
||||
if (node_column == m_map.init_index + 1) {
|
||||
m_map.add_edge(m_map.init_index, node); // 第一列节点直接与 init 连接
|
||||
return;
|
||||
}
|
||||
|
||||
// 将 image转换为二值图像后计算亮点
|
||||
BrightPointAnalyzer analyzer(image);
|
||||
|
||||
const int center_x = node_x - (m_column_offset - m_node_width) / 2; // node 与 前一列节点的中点横坐标
|
||||
const int node_y = m_map.get_node_y(node);
|
||||
Rect roi(0, 0, m_roi_margin * 2, m_roi_margin * 2);
|
||||
|
||||
// 遍历前一列节点
|
||||
const size_t pre_col_begin = m_map.get_column_begin(node_column - 1);
|
||||
const size_t pre_col_end = m_map.get_column_end(node_column - 1);
|
||||
for (size_t prev = pre_col_begin; prev < pre_col_end; ++prev) {
|
||||
const int prev_y = m_map.get_node_y(prev);
|
||||
const int center_y = (prev_y + node_y + m_node_height) / 2;
|
||||
roi.x = center_x - m_roi_margin;
|
||||
roi.y = center_y - m_roi_margin;
|
||||
analyzer.set_roi(roi);
|
||||
|
||||
if (!analyzer.analyze()) { // 节点间没有连线
|
||||
continue;
|
||||
}
|
||||
|
||||
// 按照水平方向排序(从左到右)
|
||||
std::vector<Point> brightPoints = analyzer.get_result();
|
||||
|
||||
auto [x_min_p, x_max_p] = ranges::minmax(brightPoints, /*comp=*/ {}, [](const Point& p) { return p.x; });
|
||||
const int leftmost_x = x_min_p.x;
|
||||
const int rightmost_x = x_max_p.x;
|
||||
|
||||
auto leftmostBrightPoints = brightPoints | views::filter([&](const Point& p) { return p.x == leftmost_x; });
|
||||
auto rightmostBrightPoints = brightPoints | views::filter([&](const Point& p) { return p.x == rightmost_x; });
|
||||
|
||||
auto [leftmost_y_min_p, leftmost_y_max_p] =
|
||||
ranges::minmax(leftmostBrightPoints, /*comp=*/ {}, [](const Point& p) { return p.y; });
|
||||
const int leftmost_y = (leftmost_y_min_p.y + leftmost_y_max_p.y) / 2;
|
||||
|
||||
auto [rightmost_y_min_p, rightmost_y_max_p] =
|
||||
ranges::minmax(rightmostBrightPoints, /*comp=*/ {}, [](const Point& p) { return p.y; });
|
||||
const int rightmost_y = (rightmost_y_min_p.y + rightmost_y_max_p.y) / 2;
|
||||
|
||||
if (std::abs(prev_y - node_y) < m_direction_threshold &&
|
||||
std::abs(leftmost_y - rightmost_y) < m_direction_threshold) {
|
||||
m_map.add_edge(prev, node);
|
||||
}
|
||||
else if (prev_y < node_y && leftmost_y < rightmost_y - m_direction_threshold) {
|
||||
m_map.add_edge(prev, node);
|
||||
}
|
||||
else if (prev_y > node_y && leftmost_y > rightmost_y + m_direction_threshold) {
|
||||
m_map.add_edge(prev, node);
|
||||
}
|
||||
}
|
||||
|
||||
// 同列前一个节点
|
||||
if (node > m_map.get_column_begin(node_column)) {
|
||||
size_t prev = node - 1;
|
||||
roi.x = node_x + m_node_width / 2 - m_roi_margin;
|
||||
roi.y = (m_map.get_node_y(prev) + m_node_height + m_nameplate_offset + node_y) / 2 - m_roi_margin;
|
||||
analyzer.set_roi(roi);
|
||||
if (analyzer.analyze()) {
|
||||
m_map.add_edge(prev, node);
|
||||
m_map.add_edge(node, prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void asst::RoguelikeRoutingTaskPlugin::refresh_following_combat_nodes()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
const std::string& theme = m_config->get_theme();
|
||||
|
||||
const size_t curr_node = m_map.get_curr_pos();
|
||||
const size_t curr_node_column = m_map.get_node_column(curr_node);
|
||||
|
||||
for (size_t next_node : m_map.get_node_succs(curr_node)) {
|
||||
// 不刷新同一列的节点
|
||||
const size_t next_node_column = m_map.get_node_column(next_node);
|
||||
if (next_node_column <= curr_node_column) {
|
||||
continue;
|
||||
}
|
||||
// 每个节点仅刷新一次
|
||||
if (m_map.get_node_refresh_times(next_node)) {
|
||||
continue;
|
||||
}
|
||||
// 不刷新非战斗节点
|
||||
RoguelikeNodeType next_node_type = m_map.get_node_type(next_node);
|
||||
if (next_node_type != RoguelikeNodeType::CombatOps && next_node_type != RoguelikeNodeType::EmergencyOps &&
|
||||
next_node_type != RoguelikeNodeType::DreadfulFoe) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int next_node_x = m_selected_x + (next_node_column == m_selected_column ? 0 : m_column_offset);
|
||||
int next_node_y = m_map.get_node_y(next_node);
|
||||
Rect next_node_rect = Rect(next_node_x, next_node_y, m_node_width, m_node_height);
|
||||
|
||||
// 点击节点
|
||||
ctrler()->click(next_node_rect);
|
||||
m_selected_column = m_map.get_node_column(next_node);
|
||||
update_selected_x();
|
||||
next_node_rect.x = m_selected_x;
|
||||
sleep(200);
|
||||
|
||||
// 刷新节点
|
||||
ProcessTask(*this, { m_config->get_theme() + "@Roguelike@RoutingRefreshNode" }).run();
|
||||
m_map.set_node_refresh_times(next_node, m_map.get_node_refresh_times(next_node) + 1);
|
||||
|
||||
// 识别并更新节点类型
|
||||
Matcher node_analyzer(ctrler()->get_image());
|
||||
node_analyzer.set_task_info(theme + "@Roguelike@RoutingNodeAnalyze");
|
||||
node_analyzer.set_roi(next_node_rect);
|
||||
if (node_analyzer.analyze()) {
|
||||
Matcher::Result match_results = node_analyzer.get_result();
|
||||
m_map.set_node_type(next_node, RoguelikeMapInfo.templ2type(theme, match_results.templ_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void asst::RoguelikeRoutingTaskPlugin::navigate_route()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
const size_t curr_column = m_map.get_node_column(m_map.get_curr_pos());
|
||||
|
||||
m_map.set_cost_fun([&](const RoguelikeNodePtr& node) {
|
||||
if (node->visited) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
if (node->column == curr_column) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
if (node->type == RoguelikeNodeType::CombatOps || node->type == RoguelikeNodeType::EmergencyOps ||
|
||||
node->type == RoguelikeNodeType::DreadfulFoe) {
|
||||
return 1 + (node->refresh_times ? 999 : 0);
|
||||
}
|
||||
|
||||
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) >= 1000) {
|
||||
Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@ExitThenAbandon");
|
||||
reset_in_run_variables();
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t next_node_column = m_map.get_node_column(next_node);
|
||||
const int next_node_x = m_selected_x + (next_node_column == m_selected_column ? 0 : m_column_offset);
|
||||
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);
|
||||
|
||||
if (m_map.get_node_type(next_node) == RoguelikeNodeType::Encounter) {
|
||||
Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@RoutingActionEncounter");
|
||||
m_map.set_curr_pos(next_node);
|
||||
}
|
||||
else if (m_map.get_node_type(next_node) == RoguelikeNodeType::RogueTrader) {
|
||||
Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@RoutingActionRogueTrader");
|
||||
reset_in_run_variables();
|
||||
}
|
||||
else {
|
||||
Task.set_task_base("Sarkaz@Roguelike@RoutingAction", "Sarkaz@Roguelike@ExitThenAbandon");
|
||||
reset_in_run_variables();
|
||||
}
|
||||
}
|
||||
|
||||
void asst::RoguelikeRoutingTaskPlugin::update_selected_x()
|
||||
{
|
||||
if (m_selected_column == m_map.init_index) {
|
||||
m_selected_x = m_origin_x - m_column_offset;
|
||||
}
|
||||
else if (m_selected_column == m_map.init_index + 1) {
|
||||
m_selected_x = m_origin_x;
|
||||
}
|
||||
else if (m_selected_column == m_map.get_num_columns() - 1) [[unlikely]] {
|
||||
m_selected_x = m_last_x;
|
||||
}
|
||||
else {
|
||||
m_selected_x = m_middle_x;
|
||||
}
|
||||
}
|
||||
46
src/MaaCore/Task/Roguelike/Map/RoguelikeRoutingTaskPlugin.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/AsstTypes.h"
|
||||
#include "Config/Roguelike/RoguelikeMapConfig.h"
|
||||
#include "Task/Roguelike/AbstractRoguelikeTaskPlugin.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class RoguelikeRoutingTaskPlugin : public AbstractRoguelikeTaskPlugin
|
||||
{
|
||||
public:
|
||||
using AbstractRoguelikeTaskPlugin::AbstractRoguelikeTaskPlugin;
|
||||
virtual ~RoguelikeRoutingTaskPlugin() 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;
|
||||
|
||||
protected:
|
||||
virtual bool _run() override;
|
||||
|
||||
private:
|
||||
void generate_map();
|
||||
void generate_edges(const size_t& node, const cv::Mat& image, const int& node_x);
|
||||
void refresh_following_combat_nodes();
|
||||
void navigate_route();
|
||||
void update_selected_x();
|
||||
|
||||
// ———————— constants and variables ———————————————————————————————————————————————
|
||||
RoguelikeMap m_map;
|
||||
bool m_need_generate_map = true;
|
||||
size_t m_selected_column = 0; // 当前选中节点所在列
|
||||
int m_selected_x = 0; // 当前选中节点的横坐标 (Rect.x)
|
||||
|
||||
Point m_swipe_source = { 0, 0 }; // 生成地图时向左滑动起点
|
||||
Point m_swipe_target = { 0, 0 }; // 生成地图时向左滑动终点
|
||||
int m_origin_x = 0; // 第一列节点的默认横坐标 (Rect.x)
|
||||
int m_middle_x = 0; // 中间列节点的默认横坐标 (Rect.x)
|
||||
int m_last_x = 0; // 最后列节点的默认横坐标 (Rect.x)
|
||||
int m_node_width = 0; // 节点 Rect.width
|
||||
int m_node_height = 0; // 节点 Rect.height
|
||||
int m_column_offset = 0; // 两列节点之间的距离
|
||||
int m_nameplate_offset = 0; // 节点 Rect 下边缘到节点铭牌下边缘的距离
|
||||
int m_roi_margin = 0; // roi 时的 margin offset
|
||||
int m_direction_threshold = 0; // 节点间连线方向判定的阈值
|
||||
};
|
||||
}
|
||||
@@ -28,7 +28,10 @@ namespace asst
|
||||
Collectible = 4, // 4 - 刷开局,以获得热水壶或者演讲稿开局或只凹直升,不期而遇采用保守策略
|
||||
|
||||
// ------------------ 萨米主题专用模式 ------------------
|
||||
CLP_PDS = 5 // 5 - 刷隐藏坍缩范式,以增加坍缩值为最优先目标
|
||||
CLP_PDS = 5, // 5 - 刷隐藏坍缩范式,以增加坍缩值为最优先目标
|
||||
|
||||
// ------------------ 萨卡兹主题专用模式 ------------------
|
||||
FastPass = 10001 // 10001 - 快速通过第一层
|
||||
};
|
||||
|
||||
struct RoguelikeOper
|
||||
@@ -52,7 +55,8 @@ namespace asst
|
||||
return mode == RoguelikeMode::Exp ||
|
||||
mode == RoguelikeMode::Investment ||
|
||||
mode == RoguelikeMode::Collectible ||
|
||||
(mode == RoguelikeMode::CLP_PDS && theme == RoguelikeTheme::Sami);
|
||||
(mode == RoguelikeMode::CLP_PDS && theme == RoguelikeTheme::Sami) ||
|
||||
(mode == RoguelikeMode::FastPass && theme == RoguelikeTheme::Sarkaz);
|
||||
}
|
||||
|
||||
bool verify_and_load_params(const json::value& params);
|
||||
|
||||
33
src/MaaCore/Vision/Miscellaneous/BrightPointAnalyzer.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "BrightPointAnalyzer.h"
|
||||
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/NoWarningCV.h"
|
||||
#include "Utils/Ranges.hpp"
|
||||
|
||||
using namespace asst;
|
||||
|
||||
BrightPointAnalyzer::ResultsVecOpt BrightPointAnalyzer::analyze()
|
||||
{
|
||||
const cv::Mat croppedImage = make_roi(m_image, m_roi);
|
||||
cv::Mat grayImage;
|
||||
cv::cvtColor(croppedImage, grayImage, cv::COLOR_BGR2GRAY);
|
||||
cv::Mat binaryImage;
|
||||
cv::threshold(grayImage, binaryImage, 200, 255, cv::THRESH_BINARY);
|
||||
std::vector<cv::Point> brightPoints;
|
||||
cv::findNonZero(binaryImage, brightPoints);
|
||||
|
||||
if (brightPoints.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto transform_view = brightPoints | views::transform([](const cv::Point& p) { return Point(p.x, p.y); });
|
||||
ResultsVec results(ranges::begin(transform_view), ranges::end(transform_view));
|
||||
|
||||
if (m_log_tracing) {
|
||||
Log.trace("analyze_bright_points | num:", results.size());
|
||||
}
|
||||
|
||||
// FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉
|
||||
m_result = std::move(results);
|
||||
return m_result;
|
||||
}
|
||||
24
src/MaaCore/Vision/Miscellaneous/BrightPointAnalyzer.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "Vision/VisionHelper.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class BrightPointAnalyzer : public VisionHelper
|
||||
{
|
||||
public:
|
||||
using Result = Point;
|
||||
using ResultsVec = std::vector<Result>;
|
||||
using ResultsVecOpt = std::optional<ResultsVec>;
|
||||
|
||||
using VisionHelper::VisionHelper;
|
||||
virtual ~BrightPointAnalyzer() override = default;
|
||||
|
||||
ResultsVecOpt analyze();
|
||||
// FIXME: 老接口太难重构了,先弄个这玩意兼容下,后续慢慢全删掉
|
||||
const auto& get_result() const noexcept { return m_result; }
|
||||
|
||||
private:
|
||||
ResultsVec m_result;
|
||||
};
|
||||
}
|
||||