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;