diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index c5a1a1a5d4..f6c1ba0615 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -1,127 +1,132 @@
{
- "$schema": "http://json-schema.org/draft-04/schema#",
- "title": "Build Schema",
- "$ref": "#/definitions/build",
- "definitions": {
- "build": {
- "type": "object",
- "properties": {
- "Continue": {
- "type": "boolean",
- "description": "Indicates to continue a previously failed build attempt"
- },
- "Help": {
- "type": "boolean",
- "description": "Shows the help text for this build assembly"
- },
- "Host": {
- "type": "string",
- "description": "Host for execution. Default is 'automatic'",
- "enum": [
- "AppVeyor",
- "AzurePipelines",
- "Bamboo",
- "Bitbucket",
- "Bitrise",
- "GitHubActions",
- "GitLab",
- "Jenkins",
- "Rider",
- "SpaceAutomation",
- "TeamCity",
- "Terminal",
- "TravisCI",
- "VisualStudio",
- "VSCode"
- ]
- },
- "NoLogo": {
- "type": "boolean",
- "description": "Disables displaying the NUKE logo"
- },
- "Partition": {
- "type": "string",
- "description": "Partition to use on CI"
- },
- "Plan": {
- "type": "boolean",
- "description": "Shows the execution plan (HTML)"
- },
- "Profile": {
- "type": "array",
- "description": "Defines the profiles to load",
- "items": {
- "type": "string"
- }
- },
- "Root": {
- "type": "string",
- "description": "Root directory during build execution"
- },
- "Skip": {
- "type": "array",
- "description": "List of targets to be skipped. Empty list skips all dependencies",
- "items": {
- "type": "string",
- "enum": [
- "Default",
- "DevBuild",
- "DevBuildDefault",
- "DevBuildReleaseSimulation",
- "ReleaseMaa",
- "SetMaaChangeLog",
- "SetPackageBundled",
- "SetVersion",
- "UseClean",
- "UseCommitVersion",
- "UseMaaChangeLog",
- "UseMaaDevBundle",
- "UseMaaRelease",
- "UsePublishArtifact",
- "UsePublishRelease",
- "UseRsVersion",
- "UseTagVersion",
- "WithCompileCoreRelease",
- "WithCompileWpfRelease",
- "WithSyncRes"
- ]
- }
- },
- "Target": {
- "type": "array",
- "description": "List of targets to be invoked. Default is '{default_target}'",
- "items": {
- "type": "string",
- "enum": [
- "Default",
- "DevBuild",
- "DevBuildDefault",
- "DevBuildReleaseSimulation",
- "ReleaseMaa",
- "SetMaaChangeLog",
- "SetPackageBundled",
- "SetVersion",
- "UseClean",
- "UseCommitVersion",
- "UseMaaChangeLog",
- "UseMaaDevBundle",
- "UseMaaRelease",
- "UsePublishArtifact",
- "UsePublishRelease",
- "UseRsVersion",
- "UseTagVersion",
- "WithCompileCoreRelease",
- "WithCompileWpfRelease",
- "WithSyncRes"
- ]
- }
- },
- "Verbosity": {
- "type": "string",
- "description": "Logging verbosity during build execution. Default is 'Normal'",
- "enum": ["Minimal", "Normal", "Quiet", "Verbose"]
- }
- }
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$ref": "#/definitions/build",
+ "title": "Build Schema",
+ "definitions": {
+ "build": {
+ "type": "object",
+ "properties": {
+ "Continue": {
+ "type": "boolean",
+ "description": "Indicates to continue a previously failed build attempt"
+ },
+ "Help": {
+ "type": "boolean",
+ "description": "Shows the help text for this build assembly"
+ },
+ "Host": {
+ "type": "string",
+ "description": "Host for execution. Default is 'automatic'",
+ "enum": [
+ "AppVeyor",
+ "AzurePipelines",
+ "Bamboo",
+ "Bitbucket",
+ "Bitrise",
+ "GitHubActions",
+ "GitLab",
+ "Jenkins",
+ "Rider",
+ "SpaceAutomation",
+ "TeamCity",
+ "Terminal",
+ "TravisCI",
+ "VisualStudio",
+ "VSCode"
+ ]
+ },
+ "NoLogo": {
+ "type": "boolean",
+ "description": "Disables displaying the NUKE logo"
+ },
+ "Partition": {
+ "type": "string",
+ "description": "Partition to use on CI"
+ },
+ "Plan": {
+ "type": "boolean",
+ "description": "Shows the execution plan (HTML)"
+ },
+ "Profile": {
+ "type": "array",
+ "description": "Defines the profiles to load",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Root": {
+ "type": "string",
+ "description": "Root directory during build execution"
+ },
+ "Skip": {
+ "type": "array",
+ "description": "List of targets to be skipped. Empty list skips all dependencies",
+ "items": {
+ "type": "string",
+ "enum": [
+ "Default",
+ "DevBuild",
+ "DevBuildDefault",
+ "DevBuildReleaseSimulation",
+ "ReleaseMaa",
+ "SetMaaChangeLog",
+ "SetPackageBundled",
+ "SetVersion",
+ "UseClean",
+ "UseCommitVersion",
+ "UseMaaChangeLog",
+ "UseMaaDevBundle",
+ "UseMaaRelease",
+ "UsePublishArtifact",
+ "UsePublishRelease",
+ "UseRsVersion",
+ "UseTagVersion",
+ "WithCompileCoreRelease",
+ "WithCompileWpfRelease",
+ "WithSyncRes"
+ ]
+ }
+ },
+ "Target": {
+ "type": "array",
+ "description": "List of targets to be invoked. Default is '{default_target}'",
+ "items": {
+ "type": "string",
+ "enum": [
+ "Default",
+ "DevBuild",
+ "DevBuildDefault",
+ "DevBuildReleaseSimulation",
+ "ReleaseMaa",
+ "SetMaaChangeLog",
+ "SetPackageBundled",
+ "SetVersion",
+ "UseClean",
+ "UseCommitVersion",
+ "UseMaaChangeLog",
+ "UseMaaDevBundle",
+ "UseMaaRelease",
+ "UsePublishArtifact",
+ "UsePublishRelease",
+ "UseRsVersion",
+ "UseTagVersion",
+ "WithCompileCoreRelease",
+ "WithCompileWpfRelease",
+ "WithSyncRes"
+ ]
+ }
+ },
+ "Verbosity": {
+ "type": "string",
+ "description": "Logging verbosity during build execution. Default is 'Normal'",
+ "enum": [
+ "Minimal",
+ "Normal",
+ "Quiet",
+ "Verbose"
+ ]
}
+ }
}
+ }
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f8ff0d6935..a26c4d2ffa 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -12,4 +12,4 @@
"MaaDeps/vcpkg/packages": true
},
"C_Cpp.exclusionPolicy": "checkFilesAndFolders"
-}
+}
\ No newline at end of file
diff --git a/resource/tasks/tasks.json b/resource/tasks/tasks.json
index 453cfe55e4..08405af76c 100644
--- a/resource/tasks/tasks.json
+++ b/resource/tasks/tasks.json
@@ -1253,6 +1253,11 @@
"action": "ClickRect",
"specificRect": [965, 200, 70, 50]
},
+ "FightSeries-List-Exceeded": {
+ "template": "FightSeries-List-Exceeded.png",
+ "roi": [965, 200, 70, 325],
+ "doc": "找到每一个需要补充理智的连战次数"
+ },
"StartButton1": {
"doc": "绑定插件调整连战次数",
"algorithm": "OcrDetect",
@@ -1520,6 +1525,7 @@
"next": ["CloseStonePage"]
},
"CloseStonePage": {
+ "Doc": "2025.5.5:next 在 FightSeriesAdjustPlugin 插件中被动态修改",
"algorithm": "JustReturn",
"action": "ClickRect",
"specificRect": [0, 200, 300, 400],
diff --git a/resource/template/FightSeries-List-Exceeded.png b/resource/template/FightSeries-List-Exceeded.png
new file mode 100644
index 0000000000..c7e05fe2d1
Binary files /dev/null and b/resource/template/FightSeries-List-Exceeded.png differ
diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj
index 7e69be6269..34c18b48ab 100644
--- a/src/MaaCore/MaaCore.vcxproj
+++ b/src/MaaCore/MaaCore.vcxproj
@@ -248,6 +248,7 @@
+
@@ -446,6 +447,7 @@
+
diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters
index a419bba7d0..ec4306b470 100644
--- a/src/MaaCore/MaaCore.vcxproj.filters
+++ b/src/MaaCore/MaaCore.vcxproj.filters
@@ -1328,6 +1328,9 @@
Source\Task\Roguelike
+
+ Source\Task\Fight
+
@@ -1842,6 +1845,9 @@
Source\Task\Roguelike
+
+ Source\Task\Fight
+
diff --git a/src/MaaCore/Task/Fight/FightSeriesAdjustPlugin.cpp b/src/MaaCore/Task/Fight/FightSeriesAdjustPlugin.cpp
new file mode 100644
index 0000000000..5417e51cfd
--- /dev/null
+++ b/src/MaaCore/Task/Fight/FightSeriesAdjustPlugin.cpp
@@ -0,0 +1,105 @@
+#include "FightSeriesAdjustPlugin.h"
+
+#include "Config/TaskData.h"
+#include "Controller/Controller.h"
+#include "Task/ProcessTask.h"
+#include "Utils/Logger.hpp"
+#include "Vision/MultiMatcher.h"
+#include "Vision/RegionOCRer.h"
+
+bool asst::FightSeriesAdjustPlugin::verify(AsstMsg msg, const json::value& details) const
+{
+ if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") {
+ return false;
+ }
+
+ return details.get("details", "task", "").ends_with("CloseStonePage");
+}
+
+bool asst::FightSeriesAdjustPlugin::_run()
+{
+ LogTraceFunction;
+
+ auto task = ProcessTask(*this, { "FightSeries-Open" });
+ task.run();
+
+ int exceeded_num = get_exceeded_num();
+ if (exceeded_num <= 6 && exceeded_num > 1) {
+ ProcessTask(*this, { "FightSeries-List-" + std::to_string(exceeded_num - 1) }).run();
+ auto modified_next = original_close_stone_page_next;
+ modified_next.insert(modified_next.begin(), "Fight@StartButton1");
+ Task.get("Fight@CloseStonePage")->next = modified_next;
+ }
+ else {
+ Task.get("Fight@CloseStonePage")->next = original_close_stone_page_next;
+ }
+ return true;
+}
+
+int asst::FightSeriesAdjustPlugin::get_exceeded_num() const
+{
+ const std::vector> list_y_ranges = {
+ { 505, 505 + 55 }, // List-1: [505, 560)
+ { 445, 445 + 55 }, // List-2: [445, 500)
+ { 383, 383 + 55 }, // List-3: [383, 438)
+ { 320, 320 + 55 }, // List-4: [320, 375)
+ { 260, 260 + 55 }, // List-5: [260, 315)
+ { 200, 200 + 50 } // List-6: [200, 250)
+ };
+
+ auto img = ctrler()->get_image();
+ if (img.empty()) {
+ return 0;
+ }
+ MultiMatcher multi_matcher(img);
+ multi_matcher.set_task_info("FightSeries-List-Exceeded");
+ if (!multi_matcher.analyze()) {
+ Log.info(__FUNCTION__, "Fight series exceeded analyze failed");
+ LogInfo << __FUNCTION__ << "Falling back to OCR for exceeded number detection.";
+
+ std::vector detected_numbers;
+
+ for (const auto& range : list_y_ranges) {
+ RegionOCRer analyzer(img);
+ analyzer.set_task_info("NumberOcrReplace");
+ analyzer.set_bin_threshold(150, 255);
+ analyzer.set_roi({ 980, range.first, 40, range.second - range.first });
+
+ auto result_opt = analyzer.analyze();
+ if (result_opt && !result_opt->text.empty()) {
+ try {
+ int number = std::stoi(result_opt->text);
+ detected_numbers.push_back(number);
+ }
+ catch (const std::invalid_argument&) {
+ LogInfo << __FUNCTION__ << "OCR failed to parse number: " << result_opt->text;
+ }
+ }
+ }
+
+ if (!detected_numbers.empty()) {
+ int max_number = *std::max_element(detected_numbers.begin(), detected_numbers.end());
+ LogInfo << __FUNCTION__ << "Detected max number via OCR: " << max_number;
+ return max_number + 1;
+ }
+
+ return -1;
+ }
+ auto match_result = multi_matcher.get_result();
+ if (match_result.empty()) {
+ LogInfo << __FUNCTION__ << "No matches found for FightSeries-List-Exceeded.";
+ return -1;
+ }
+ sort_by_vertical_(match_result);
+ int exceeded_y_pos = match_result.back().rect.y;
+ for (size_t i = 0; i < list_y_ranges.size(); ++i) {
+ const auto& range = list_y_ranges[i];
+ if (exceeded_y_pos >= range.first && exceeded_y_pos < range.second) {
+ int list_index = static_cast(i + 1); // 列表序号从 1 开始
+ LogInfo << __FUNCTION__ << "Exceeded icon y=" << exceeded_y_pos << " is within List-" << list_index
+ << " range [" << range.first << ", " << range.second << ").";
+ return list_index;
+ }
+ }
+ return 0;
+}
diff --git a/src/MaaCore/Task/Fight/FightSeriesAdjustPlugin.h b/src/MaaCore/Task/Fight/FightSeriesAdjustPlugin.h
new file mode 100644
index 0000000000..4dd78f0727
--- /dev/null
+++ b/src/MaaCore/Task/Fight/FightSeriesAdjustPlugin.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "Config/TaskData.h"
+#include "Task/AbstractTaskPlugin.h"
+
+namespace asst
+{
+class FightSeriesAdjustPlugin final : public AbstractTaskPlugin
+{
+public:
+ using AbstractTaskPlugin::AbstractTaskPlugin;
+ virtual ~FightSeriesAdjustPlugin() override = default;
+
+ virtual bool verify(AsstMsg msg, const json::value& details) const override;
+
+protected:
+ virtual bool _run() override;
+
+private:
+ // 临时处理
+ std::vector original_close_stone_page_next = { "Fight@StageSNReturnFlag", "Fight@Stop" };
+ int get_exceeded_num() const;
+};
+}
diff --git a/src/MaaCore/Task/Fight/FightTimesTaskPlugin.cpp b/src/MaaCore/Task/Fight/FightTimesTaskPlugin.cpp
index 6cedcc4878..c890fc008e 100644
--- a/src/MaaCore/Task/Fight/FightTimesTaskPlugin.cpp
+++ b/src/MaaCore/Task/Fight/FightTimesTaskPlugin.cpp
@@ -38,3 +38,4 @@ bool asst::FightTimesTaskPlugin::_run()
inited = true;
return true;
}
+
diff --git a/src/MaaCore/Task/Interface/FightTask.cpp b/src/MaaCore/Task/Interface/FightTask.cpp
index de518f7d6e..f87031f6eb 100644
--- a/src/MaaCore/Task/Interface/FightTask.cpp
+++ b/src/MaaCore/Task/Interface/FightTask.cpp
@@ -4,6 +4,7 @@
#include "Config/TaskData.h"
#include "Task/Fight/DrGrandetTaskPlugin.h"
+#include "Task/Fight/FightSeriesAdjustPlugin.h"
#include "Task/Fight/FightTimesTaskPlugin.h"
#include "Task/Fight/MedicineCounterTaskPlugin.h"
#include "Task/Fight/SanityBeforeStageTaskPlugin.h"
@@ -54,6 +55,8 @@ asst::FightTask::FightTask(const AsstCallback& callback, Assistant* inst) :
m_fight_times_task_plugin_prt = m_fight_task_ptr->register_plugin();
m_fight_times_task_plugin_prt->set_retry_times(3);
m_medicine_plugin = m_fight_task_ptr->register_plugin();
+ m_fight_series_adjust_plugin_ptr = m_fight_task_ptr->register_plugin();
+ m_fight_series_adjust_plugin_ptr->set_retry_times(3);
m_subtasks.emplace_back(m_start_up_task_ptr);
m_subtasks.emplace_back(m_stage_navigation_task_ptr);
diff --git a/src/MaaCore/Task/Interface/FightTask.h b/src/MaaCore/Task/Interface/FightTask.h
index 6b0848c4cd..9372c64c02 100644
--- a/src/MaaCore/Task/Interface/FightTask.h
+++ b/src/MaaCore/Task/Interface/FightTask.h
@@ -12,6 +12,7 @@ class StageNavigationTask;
class DrGrandetTaskPlugin;
class SideStoryReopenTask;
class MedicineCounterTaskPlugin;
+class FightSeriesAdjustPlugin;
class FightTask final : public InterfaceTask
{
@@ -28,6 +29,7 @@ protected:
std::shared_ptr m_stage_navigation_task_ptr = nullptr;
std::shared_ptr m_fight_task_ptr = nullptr;
std::shared_ptr m_fight_times_task_plugin_prt = nullptr;
+ std::shared_ptr m_fight_series_adjust_plugin_ptr = nullptr;
std::shared_ptr m_medicine_plugin = nullptr;
std::shared_ptr m_stage_drops_plugin_ptr = nullptr;
std::shared_ptr m_dr_grandet_task_plugin_ptr = nullptr;