mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-20 02:55:38 +08:00
feat: 自动编队允许指定助战单位 (#10820)
* feat: 自动编队自动借助战 Revertd78fc68fix: 招募助战后继续(缺少点击快速招募) i18n: 翻译 style: clang-format fix: basetask template fix: werror perf: 注释、format i18n: en perf: 完成功能实现,复用task,进行gui输出 perf: 更改默认状态 feat: BattleFormationTask Revert05baeb4(Back to739aa03) * fix: 缺干员回到主界面的时候应该点确认而不是返回 * feat: 助战干员职业选择栏模版 * refactor: UseSupportUnitTaskPlugin * chore: Auto update by pre-commit hooks [skip changelog] * perf: 默认值适配国服 * chore: Auto update by pre-commit hooks [skip changelog] * feat: try_find_and_apply_support_unit * chore: Auto update by pre-commit hooks [skip changelog] * fix: 大概可以用了 * fix: 补充 struct 的构造函数 * chore: Auto update by pre-commit hooks [skip changelog] * fix: 添加默认构造函数 * chore: Auto update by pre-commit hooks [skip changelog] * fix: 改改类型 * fix: callback[what] 纠正 * perf: roi及初始化优化 * perf: 简化中英文查找 * perf: rename func * perf: click roi * fix: 在点击助战干员的行动后添加 LoadingText * fix: 打点 Log * fix: remove unused function --------- Co-authored-by: Weiyou Wang <44151844+Alan-Charred@users.noreply.github.com> Co-authored-by: status102 <102887808+status102@users.noreply.github.com>
This commit is contained in:
@@ -131,6 +131,22 @@ inline static LocationType get_role_usual_location(const Role& role)
|
||||
}
|
||||
}
|
||||
|
||||
struct RequiredOper // 编队/招募需求干员
|
||||
{
|
||||
Role role = Role::Unknown;
|
||||
std::string name;
|
||||
int skill = 0; // 技能序号,取值范围 [0, 3],0时使用默认技能 或 上次编队时使用的技能
|
||||
|
||||
RequiredOper() = default;
|
||||
|
||||
RequiredOper(Role role_, std::string name_, int skill_) :
|
||||
role(role_),
|
||||
name(std::move(name_)),
|
||||
skill(skill_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct DeploymentOper
|
||||
{
|
||||
size_t index = 0;
|
||||
@@ -341,17 +357,21 @@ struct RefreshSupportInfo
|
||||
|
||||
namespace asst
|
||||
{
|
||||
inline std::string enum_to_string(asst::battle::Role role)
|
||||
inline std::string enum_to_string(asst::battle::Role role, bool en = false)
|
||||
{
|
||||
using asst::battle::Role;
|
||||
static const std::unordered_map<Role, std::string> RoleToName = {
|
||||
{ Role::Warrior, "近卫" }, { Role::Pioneer, "先锋" }, { Role::Medic, "医疗" },
|
||||
{ Role::Tank, "重装" }, { Role::Sniper, "狙击" }, { Role::Caster, "术师" },
|
||||
{ Role::Support, "辅助" }, { Role::Special, "特种" }, { Role::Drone, "无人机" },
|
||||
static const std::unordered_map<Role, std::pair<std::string, std::string>> RoleToName {
|
||||
{ Role::Warrior, { "近卫", "Warrior" } }, { Role::Pioneer, { "先锋", "Pioneer" } },
|
||||
{ Role::Medic, { "医疗", "Medic" } }, { Role::Tank, { "重装", "Tank" } },
|
||||
{ Role::Sniper, { "狙击", "Sniper" } }, { Role::Caster, { "术师", "Caster" } },
|
||||
{ Role::Support, { "辅助", "Support" } }, { Role::Special, { "特种", "Special" } },
|
||||
{ Role::Drone, { "无人机", "Drone" } },
|
||||
};
|
||||
|
||||
if (auto iter = RoleToName.find(role); iter != RoleToName.end()) {
|
||||
return iter->second;
|
||||
return en ? iter->second.second : iter->second.first;
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
<ClInclude Include="Utils\Algorithm.hpp" />
|
||||
<ClInclude Include="Utils\File.hpp" />
|
||||
<ClInclude Include="Utils\LibraryHolder.hpp" />
|
||||
<ClInclude Include="Vision\Battle\SupportListAnalyzer.h" />
|
||||
<ClInclude Include="Vision\Roguelike\RoguelikeParameterAnalyzer.h" />
|
||||
<ClInclude Include="Vision\VisionHelper.h" />
|
||||
<ClInclude Include="Vision\Battle\BattleFormationAnalyzer.h" />
|
||||
@@ -199,6 +200,7 @@
|
||||
<ClInclude Include="Task\Interface\StartUpTask.h" />
|
||||
<ClInclude Include="Task\Miscellaneous\AutoRecruitTask.h" />
|
||||
<ClInclude Include="Task\Miscellaneous\BattleFormationTask.h" />
|
||||
<ClInclude Include="Task\Miscellaneous\UseSupportUnitTaskPlugin.h" />
|
||||
<ClInclude Include="Task\Miscellaneous\BattleProcessTask.h" />
|
||||
<ClInclude Include="Task\Miscellaneous\CreditFightTask.h" />
|
||||
<ClInclude Include="Task\Miscellaneous\CreditShoppingTask.h" />
|
||||
@@ -302,6 +304,7 @@
|
||||
<ClCompile Include="Task\SSS\SSSBattleProcessTask.cpp" />
|
||||
<ClCompile Include="Task\SSS\SSSDropRewardsTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\SSS\SSSStageManagerTask.cpp" />
|
||||
<ClCompile Include="Vision\Battle\SupportListAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\Roguelike\RoguelikeParameterAnalyzer.cpp" />
|
||||
<ClCompile Include="Vision\VisionHelper.cpp" />
|
||||
<ClCompile Include="Vision\Battle\BattleFormationAnalyzer.cpp" />
|
||||
@@ -384,6 +387,7 @@
|
||||
<ClCompile Include="Task\Interface\StartUpTask.cpp" />
|
||||
<ClCompile Include="Task\Miscellaneous\AutoRecruitTask.cpp" />
|
||||
<ClCompile Include="Task\Miscellaneous\BattleFormationTask.cpp" />
|
||||
<ClCompile Include="Task\Miscellaneous\UseSupportUnitTaskPlugin.cpp" />
|
||||
<ClCompile Include="Task\Miscellaneous\BattleProcessTask.cpp" />
|
||||
<ClCompile Include="Task\Miscellaneous\CreditFightTask.cpp" />
|
||||
<ClCompile Include="Task\Miscellaneous\CreditShoppingTask.cpp" />
|
||||
|
||||
@@ -441,6 +441,9 @@
|
||||
<ClInclude Include="Task\Miscellaneous\BattleFormationTask.h">
|
||||
<Filter>Source\Task\Miscellaneous</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Task\Miscellaneous\UseSupportUnitTaskPlugin.h">
|
||||
<Filter>Source\Task\Miscellaneous</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Task\Miscellaneous\BattleProcessTask.h">
|
||||
<Filter>Source\Task\Miscellaneous</Filter>
|
||||
</ClInclude>
|
||||
@@ -731,6 +734,9 @@
|
||||
<ClInclude Include="LDExtras.h">
|
||||
<Filter>Source\Controller</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Vision\Battle\SupportListAnalyzer.h">
|
||||
<Filter>Source\Vision\Battle</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Vision\VisionHelper.cpp">
|
||||
@@ -958,6 +964,9 @@
|
||||
<ClCompile Include="Task\Miscellaneous\BattleFormationTask.cpp">
|
||||
<Filter>Source\Task\Miscellaneous</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Task\Miscellaneous\UseSupportUnitTaskPlugin.cpp">
|
||||
<Filter>Source\Task\Miscellaneous</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Task\Miscellaneous\BattleProcessTask.cpp">
|
||||
<Filter>Source\Task\Miscellaneous</Filter>
|
||||
</ClCompile>
|
||||
@@ -1227,5 +1236,8 @@
|
||||
<ClCompile Include="LDExtras.cpp">
|
||||
<Filter>Source\Controller</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Vision\Battle\SupportListAnalyzer.cpp">
|
||||
<Filter>Source\Vision\Battle</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -64,6 +64,8 @@ bool asst::CopilotTask::set_params(const json::value& params)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
using SupportUnitUsage = BattleFormationTask::SupportUnitUsage;
|
||||
|
||||
if (m_running) {
|
||||
return false;
|
||||
}
|
||||
@@ -85,6 +87,8 @@ bool asst::CopilotTask::set_params(const json::value& params)
|
||||
int select_formation = params.get("select_formation", 0); // 选择第几个编队,0为不选择
|
||||
bool add_trust = params.get("add_trust", false); // 是否自动补信赖
|
||||
bool add_user_additional = params.contains("user_additional"); // 是否自动补用户自定义干员
|
||||
auto support_unit_usage = static_cast<SupportUnitUsage>(
|
||||
params.get("support_unit_usage", static_cast<int>(SupportUnitUsage::WhenNeeded))); // 助战干员使用模式
|
||||
std::string support_unit_name = params.get("support_unit_name", std::string());
|
||||
|
||||
if (params.contains("add_user_additional")) {
|
||||
@@ -145,7 +149,8 @@ bool asst::CopilotTask::set_params(const json::value& params)
|
||||
m_formation_task_ptr->set_enable(with_formation && navigate_name.find("TR") == std::string::npos);
|
||||
m_formation_task_ptr->set_select_formation(select_formation);
|
||||
m_formation_task_ptr->set_add_trust(add_trust);
|
||||
m_formation_task_ptr->set_support_unit_name(std::move(support_unit_name));
|
||||
m_formation_task_ptr->set_support_unit_usage(support_unit_usage);
|
||||
m_formation_task_ptr->set_specific_support_unit(support_unit_name);
|
||||
|
||||
if (auto opt = params.find<json::array>("user_additional"); add_user_additional && opt) {
|
||||
std::vector<std::pair<std::string, int>> user_additional;
|
||||
|
||||
@@ -9,10 +9,41 @@
|
||||
#include "Config/TaskData.h"
|
||||
#include "Controller/Controller.h"
|
||||
#include "Task/ProcessTask.h"
|
||||
#include "UseSupportUnitTaskPlugin.h"
|
||||
#include "Utils/ImageIo.hpp"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Vision/MultiMatcher.h"
|
||||
|
||||
asst::BattleFormationTask::BattleFormationTask(
|
||||
const AsstCallback& callback,
|
||||
Assistant* inst,
|
||||
std::string_view task_chain) :
|
||||
AbstractTask(callback, inst, task_chain),
|
||||
m_use_support_unit_task_ptr(std::make_shared<UseSupportUnitTaskPlugin>(callback, inst, task_chain))
|
||||
{
|
||||
}
|
||||
|
||||
bool asst::BattleFormationTask::set_specific_support_unit(const std::string& name)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (m_support_unit_usage != SupportUnitUsage::Specific) {
|
||||
Log.error(__FUNCTION__, "| Current support unit usage is not SupportUnitUsage::Specific");
|
||||
return false;
|
||||
}
|
||||
|
||||
const battle::Role role = (m_specific_support_unit.role = BattleData.get_role(name));
|
||||
if (role == battle::Role::Unknown) {
|
||||
// 无法根据干员名称获取其职业
|
||||
Log.error(__FUNCTION__, "| Invalid specific support unit");
|
||||
return false;
|
||||
}
|
||||
m_specific_support_unit.name = name; // 此处可能需要对阿米娅进行特殊处理
|
||||
// 之后在 parse_formation 中,如果发现这名助战干员,则将其技能设定为对应的所需技能
|
||||
m_specific_support_unit.skill = 0;
|
||||
return true;
|
||||
};
|
||||
|
||||
bool asst::BattleFormationTask::_run()
|
||||
{
|
||||
LogTraceFunction;
|
||||
@@ -34,13 +65,40 @@ bool asst::BattleFormationTask::_run()
|
||||
add_formation(role, oper_groups, missing_operators);
|
||||
}
|
||||
|
||||
if (!missing_operators.empty()) {
|
||||
if (missing_operators.size() == 1) {
|
||||
// TODO: 自动借助战?
|
||||
// 在有且仅有一个缺失干员组时尝试寻找助战干员补齐编队
|
||||
if (use_suppprt_unit_when_needed() && missing_operators.size() == 1 && !m_used_support_unit) {
|
||||
// 之后再重构数据结构,先凑合用
|
||||
std::vector<battle::RequiredOper> required_opers;
|
||||
for (const battle::OperUsage& oper : missing_operators.front().second) {
|
||||
// 如果指定助战干员正好可以补齐编队,则只招募指定助战干员就好了,记得再次确认一下 skill
|
||||
// 如果编队里正好有【艾雅法拉 - 2】和 【艾雅法拉 - 3】呢?
|
||||
if (oper.name == m_specific_support_unit.name) {
|
||||
m_specific_support_unit.skill = oper.skill;
|
||||
required_opers.clear();
|
||||
required_opers.emplace_back(m_specific_support_unit);
|
||||
break;
|
||||
}
|
||||
required_opers.emplace_back(BattleData.get_role(oper.name), oper.name, oper.skill);
|
||||
}
|
||||
|
||||
report_missing_operators(missing_operators);
|
||||
// 先退出去招募助战再回来,好蠢
|
||||
confirm_selection();
|
||||
Log.info(__FUNCTION__, "| Left quick formation scene");
|
||||
if (m_use_support_unit_task_ptr->try_add_support_unit(required_opers, 5, true)) {
|
||||
m_used_support_unit = true;
|
||||
missing_operators.clear();
|
||||
}
|
||||
// 再到快速编队页面
|
||||
if (!enter_selection_page()) {
|
||||
save_img(utils::path("debug") / utils::path("other"));
|
||||
return false;
|
||||
}
|
||||
Log.info(__FUNCTION__, "| Returned to quick formation scene");
|
||||
}
|
||||
|
||||
// 在尝试补齐编队后依然有缺失干员,自动编队失败
|
||||
if (!missing_operators.empty()) {
|
||||
report_missing_operators(missing_operators);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -71,11 +129,11 @@ bool asst::BattleFormationTask::_run()
|
||||
}
|
||||
confirm_selection();
|
||||
|
||||
// 借一个随机助战
|
||||
if (m_support_unit_name == "_RANDOM_") {
|
||||
if (!select_random_support_unit()) {
|
||||
return false;
|
||||
}
|
||||
if (m_support_unit_usage == SupportUnitUsage::Specific && !m_used_support_unit) { // 使用指定助战干员
|
||||
m_used_support_unit = m_use_support_unit_task_ptr->try_add_support_unit({ m_specific_support_unit }, 5, true);
|
||||
}
|
||||
else if (m_support_unit_usage == SupportUnitUsage::Random && !m_used_support_unit) { // 使用随机助战干员
|
||||
m_used_support_unit = m_use_support_unit_task_ptr->try_add_support_unit({}, 5, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -427,6 +485,11 @@ bool asst::BattleFormationTask::parse_formation()
|
||||
battle::Role role = BattleData.get_role(opers_vec.front().name);
|
||||
for (const auto& oper : opers_vec) {
|
||||
same_role &= BattleData.get_role(oper.name) == role;
|
||||
|
||||
// (仅一次)如果发现这名助战干员,则将其技能设定为对应的所需技能
|
||||
if (oper.name == m_specific_support_unit.name && m_specific_support_unit.skill == 0) {
|
||||
m_specific_support_unit.skill = oper.skill;
|
||||
}
|
||||
}
|
||||
|
||||
// for unknown, will use { "BattleQuickFormationRole-All", "BattleQuickFormationRole-All-OCR" }
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class UseSupportUnitTaskPlugin;
|
||||
|
||||
class BattleFormationTask : public AbstractTask
|
||||
{
|
||||
public:
|
||||
using AbstractTask::AbstractTask;
|
||||
BattleFormationTask(const AsstCallback& callback, Assistant* inst, std::string_view task_chain);
|
||||
virtual ~BattleFormationTask() override = default;
|
||||
|
||||
enum class Filter
|
||||
@@ -29,8 +31,6 @@ public:
|
||||
|
||||
void append_additional_formation(AdditionalFormation formation) { m_additional.emplace_back(std::move(formation)); }
|
||||
|
||||
void set_support_unit_name(std::string name) { m_support_unit_name = std::move(name); }
|
||||
|
||||
// 设置追加自定干员列表
|
||||
void set_user_additional(std::vector<std::pair<std::string, int>> value) { m_user_additional = std::move(value); }
|
||||
|
||||
@@ -53,6 +53,31 @@ public:
|
||||
|
||||
void set_data_resource(DataResource resource) { m_data_resource = resource; }
|
||||
|
||||
// ————————————————————————————————
|
||||
// 助战干员选择相关
|
||||
// ————————————————————————————————
|
||||
enum class SupportUnitUsage // 助战干员使用策略
|
||||
{
|
||||
None = 0, // 不使用助战干员
|
||||
WhenNeeded = 1, // 如果有且仅有一名缺失干员则尝试寻找助战干员补齐编队, 如果无缺失干员则不使用助战干员
|
||||
Specific = 2, // 如果有且仅有一名缺失干员则尝试寻找助战干员补齐编队,如果无缺失干员则使用指定助战干员
|
||||
Random = 3 // 如果有且仅有一名缺失干员则尝试寻找助战干员补齐编队,如果无缺失干员则使用随机助战干员
|
||||
};
|
||||
|
||||
void set_support_unit_usage(const SupportUnitUsage& support_unit_usgae)
|
||||
{
|
||||
m_support_unit_usage = support_unit_usgae;
|
||||
}
|
||||
|
||||
// 是否在有且仅有一名缺失干员时尝试寻找助战干员补齐编队
|
||||
bool use_suppprt_unit_when_needed() const
|
||||
{
|
||||
return m_support_unit_usage == SupportUnitUsage::WhenNeeded ||
|
||||
m_support_unit_usage == SupportUnitUsage::Specific || m_support_unit_usage == SupportUnitUsage::Random;
|
||||
}
|
||||
|
||||
bool set_specific_support_unit(const std::string& name = ""); // 设置指定助战干员
|
||||
|
||||
protected:
|
||||
using OperGroup = std::pair<std::string, std::vector<asst::battle::OperUsage>>;
|
||||
|
||||
@@ -83,11 +108,19 @@ protected:
|
||||
std::make_shared<std::unordered_map<std::string, std::string>>(); // 编队中的干员名称-所属组名
|
||||
bool m_add_trust = false; // 是否需要追加信赖干员
|
||||
std::vector<std::pair<std::string, int>> m_user_additional; // 追加干员表,从头往后加
|
||||
std::string m_support_unit_name;
|
||||
DataResource m_data_resource = DataResource::Copilot;
|
||||
std::vector<AdditionalFormation> m_additional;
|
||||
std::string m_last_oper_name;
|
||||
int m_select_formation_index = 0;
|
||||
int m_missing_retry_times = 1; // 识别不到干员的重试次数
|
||||
|
||||
// ————————————————————————————————
|
||||
// 助战干员选择相关
|
||||
// ————————————————————————————————
|
||||
std::shared_ptr<UseSupportUnitTaskPlugin> m_use_support_unit_task_ptr = nullptr;
|
||||
SupportUnitUsage m_support_unit_usage = SupportUnitUsage::None;
|
||||
bool m_used_support_unit = false; // 是否已经招募助战干员
|
||||
// ———————— 以下变量为指定助战干员设置,仅当 m_support_unit_usage == SupportUnitUsage::Specific 时有效 ————————
|
||||
battle::RequiredOper m_specific_support_unit;
|
||||
};
|
||||
} // namespace asst
|
||||
|
||||
@@ -23,7 +23,7 @@ asst::CreditFightTask::CreditFightTask(const AsstCallback& callback, Assistant*
|
||||
json::value copilot_params = json::object {
|
||||
{ "filename", utils::path_to_utf8_string(ResDir.get() / "copilot" / "OF-1_credit_fight.json") },
|
||||
{ "formation", true },
|
||||
{ "support_unit_name", "_RANDOM_" },
|
||||
{ "support_unit_usage", 3 },
|
||||
};
|
||||
m_copilot_task_ptr->set_params(copilot_params);
|
||||
|
||||
@@ -48,7 +48,7 @@ void asst::CreditFightTask::set_select_formation(int index)
|
||||
json::value copilot_params = json::object {
|
||||
{ "filename", utils::path_to_utf8_string(ResDir.get() / "copilot" / "OF-1_credit_fight.json") },
|
||||
{ "formation", true },
|
||||
{ "support_unit_name", "_RANDOM_" },
|
||||
{ "support_unit_usage", 3 },
|
||||
{ "select_formation", index },
|
||||
};
|
||||
m_copilot_task_ptr->set_params(copilot_params);
|
||||
|
||||
196
src/MaaCore/Task/Miscellaneous/UseSupportUnitTaskPlugin.cpp
Normal file
196
src/MaaCore/Task/Miscellaneous/UseSupportUnitTaskPlugin.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "UseSupportUnitTaskPlugin.h"
|
||||
|
||||
#include "Config/GeneralConfig.h"
|
||||
#include "Config/Miscellaneous/BattleDataConfig.h"
|
||||
#include "Controller/Controller.h"
|
||||
#include "Task/ProcessTask.h"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Vision/Matcher.h"
|
||||
|
||||
bool asst::UseSupportUnitTaskPlugin::try_add_support_unit(
|
||||
const std::vector<RequiredOper>& required_opers,
|
||||
const int max_refresh_times,
|
||||
const bool max_spec_lvl,
|
||||
const bool allow_non_friend_support_unit)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
// 通过点击编队界面右上角 <助战单位> 文字左边的 Icon 进入助战干员选择界面
|
||||
ProcessTask(*this, { "UseSupportUnit-Enter" }).run();
|
||||
|
||||
// 随机模式
|
||||
if (required_opers.empty()) {
|
||||
return try_add_support_unit_for_role(
|
||||
Role::Unknown,
|
||||
required_opers,
|
||||
max_refresh_times,
|
||||
max_spec_lvl,
|
||||
allow_non_friend_support_unit);
|
||||
}
|
||||
|
||||
// 非随机模式
|
||||
std::vector<RequiredOper> temp_required_opers;
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
if (i >= required_opers.size()) {
|
||||
break;
|
||||
}
|
||||
temp_required_opers.emplace_back(required_opers[i]);
|
||||
if (try_add_support_unit_for_role(
|
||||
required_opers[i].role,
|
||||
temp_required_opers,
|
||||
max_refresh_times,
|
||||
max_spec_lvl,
|
||||
allow_non_friend_support_unit)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 未找到符合要求的助战干员,手动退出助战列表
|
||||
Log.info(__FUNCTION__, "| Fail to find any qualified support operator");
|
||||
ProcessTask(*this, { "UseSupportUnit-LeaveSupportList" }).run();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool asst::UseSupportUnitTaskPlugin::try_add_support_unit_for_role(
|
||||
const Role role,
|
||||
const std::vector<RequiredOper>& required_opers,
|
||||
const int max_refresh_times,
|
||||
const bool max_spec_lvl,
|
||||
const bool allow_non_friend_support_unit)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
// 从 required_opers 中筛除职业不为 role 的干员
|
||||
auto filtered_view = required_opers | views::filter([&](const RequiredOper& required_oper) {
|
||||
return role == Role::Unknown || required_oper.role == role;
|
||||
});
|
||||
std::vector<battle::RequiredOper> filtered_required_opers(filtered_view.begin(), filtered_view.end());
|
||||
|
||||
// 若 role != Role::Unknown 则切换到对应职业的助战干员列表,否则保留当前职业选择
|
||||
if (role != battle::Role::Unknown) {
|
||||
ProcessTask(*this, { enum_to_string(role, true) + "@UseSupportUnit-Role" }).run();
|
||||
}
|
||||
|
||||
// 定位到助战列表最左侧第一页
|
||||
for (int i = 0; i < MaxNumSupportListPages - 1; ++i) {
|
||||
ProcessTask(*this, { "UseSupportUnit-SwipeToTheLeft" }).run();
|
||||
}
|
||||
|
||||
// 初始化变量
|
||||
SupportListAnalyzer analyzer;
|
||||
std::unordered_set<std::string> known_oper_names;
|
||||
std::vector<std::optional<size_t>> candidates(filtered_required_opers.size(), std::nullopt);
|
||||
|
||||
for (int refresh_times = 0; refresh_times <= max_refresh_times && !need_exit(); ++refresh_times) {
|
||||
for (int page = 0; page < MaxNumSupportListPages && !need_exit(); ++page) {
|
||||
// Step 1: 获取助战干员列表
|
||||
analyzer.set_image(ctrler()->get_image());
|
||||
// 未识别到任何助战干员,极有可能当前不在助战列表界面,报错退出
|
||||
if (!analyzer.analyze(known_oper_names)) [[unlikely]] {
|
||||
Log.error(__FUNCTION__, "| Fail to analyze support list");
|
||||
return false;
|
||||
}
|
||||
std::vector<SupportUnit> support_list = analyzer.get_result();
|
||||
|
||||
// Step 2: 筛选助战干员列表
|
||||
for (size_t index = 0; index < support_list.size(); ++index) {
|
||||
const SupportUnit& support_unit = support_list[index];
|
||||
known_oper_names.insert(support_unit.name);
|
||||
|
||||
// 若 support_unit 满足以下筛选条件:
|
||||
// 1. 当 max_spec_lvl == true 时,精英化等级达到 2;
|
||||
// 2. 当 allow_non_friend_support_unit == false 时,必须满足 support_unit.from_friend == true;
|
||||
// 且存在 filtered_required_opers[i] 与之匹配:
|
||||
// 1. filtered_required_opers[i].name == name;
|
||||
// 2. support_unit.elite >= filtered_required_opers[i].skill - 1;
|
||||
// 则将 candidates[i] 设置为 index。
|
||||
if (max_spec_lvl && support_unit.elite == 2) {
|
||||
continue;
|
||||
}
|
||||
if (!allow_non_friend_support_unit && !support_unit.from_friend) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 若 role == Role::Unknown 则选择这名干员,使用其默认技能
|
||||
if (role == Role::Unknown) {
|
||||
return try_use_support_unit_with_skill(support_unit, 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < filtered_required_opers.size(); ++i) {
|
||||
const RequiredOper& required_oper = filtered_required_opers[i];
|
||||
if (role == required_oper.role && support_unit.name == required_oper.name &&
|
||||
support_unit.elite >= required_oper.skill - 1) {
|
||||
candidates[i] = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: 依次点选筛选出的助战干员,根据需要判断技能是否为专三,并使用
|
||||
for (size_t i = 0; i < filtered_required_opers.size(); ++i) {
|
||||
if (!candidates[i].has_value()) {
|
||||
continue;
|
||||
}
|
||||
const RequiredOper& required_oper = filtered_required_opers[i];
|
||||
const SupportUnit& support_unit = support_list[candidates[i].value()];
|
||||
if (try_use_support_unit_with_skill(support_unit, required_oper.skill, max_spec_lvl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 candidate
|
||||
candidates.assign(filtered_required_opers.size(), std::nullopt);
|
||||
// 未滑到尾页,滑到下一页
|
||||
if (page < MaxNumSupportListPages - 1) {
|
||||
ProcessTask(*this, { "UseSupportUnit-SwipeToTheRight" }).run();
|
||||
}
|
||||
} // inner for loop to iterate through support list pages
|
||||
// 重新定位到助战列表最左侧第一页
|
||||
for (int i = 0; i < MaxNumSupportListPages - 1; ++i) {
|
||||
ProcessTask(*this, { "UseSupportUnit-SwipeToTheLeft" }).run();
|
||||
}
|
||||
// 更新助战列表
|
||||
if (refresh_times < max_refresh_times) {
|
||||
ProcessTask(*this, { "UseSupportUnit-RefreshSupportList" }).run();
|
||||
}
|
||||
} // outer for loop to iterate until reaching refresh_times
|
||||
return false;
|
||||
}
|
||||
|
||||
bool asst::UseSupportUnitTaskPlugin::try_use_support_unit_with_skill(
|
||||
const SupportUnit& support_unit,
|
||||
int skill,
|
||||
bool max_spec_lvl)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
// 点选被选择的助战干员
|
||||
ctrler()->click(support_unit.rect);
|
||||
sleep(Config.get_options().task_delay);
|
||||
ProcessTask(*this, { "Stop@LoadingText", "Stop" }).run();
|
||||
|
||||
if (skill != 0) {
|
||||
if (max_spec_lvl) {
|
||||
// 判断所需技能是否为专三
|
||||
Matcher max_spec_lvl_analyzer(ctrler()->get_image());
|
||||
max_spec_lvl_analyzer.set_task_info("UseSupportUnit-MaxSpecLvl-" + std::to_string(skill));
|
||||
if (!max_spec_lvl_analyzer.analyze()) { // 所需技能非专三
|
||||
// 取消选择
|
||||
ProcessTask(*this, { "UseSupportUnit-Cancel" }).run();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 选择技能
|
||||
ProcessTask(*this, { "UseSupportUnit-SelectSkill-" + std::to_string(skill) }).run();
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
ProcessTask(*this, { "UseSupportUnit-Confirm" }).run();
|
||||
|
||||
// callback
|
||||
json::value info = basic_info_with_what("RecruitSuppportOperator");
|
||||
info["details"]["name"] = support_unit.name;
|
||||
info["details"]["skill"] = skill;
|
||||
callback(AsstMsg::SubTaskExtraInfo, info);
|
||||
|
||||
return true;
|
||||
}
|
||||
81
src/MaaCore/Task/Miscellaneous/UseSupportUnitTaskPlugin.h
Normal file
81
src/MaaCore/Task/Miscellaneous/UseSupportUnitTaskPlugin.h
Normal file
@@ -0,0 +1,81 @@
|
||||
// 助战干员选择插件
|
||||
#pragma once
|
||||
|
||||
#include "Common/AsstBattleDef.h"
|
||||
#include "Task/AbstractTask.h"
|
||||
#include "Vision/Battle/SupportListAnalyzer.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class UseSupportUnitTaskPlugin : public AbstractTask
|
||||
{
|
||||
public:
|
||||
using AbstractTask::AbstractTask;
|
||||
virtual ~UseSupportUnitTaskPlugin() override = default;
|
||||
|
||||
using Role = battle::Role;
|
||||
using RequiredOper = battle::RequiredOper;
|
||||
using SupportUnit = SupportListAnalyzer::SupportUnit;
|
||||
|
||||
bool try_add_support_unit(
|
||||
const std::vector<RequiredOper>& required_opers = {},
|
||||
int max_refresh_times = 0,
|
||||
bool max_spec_lvl = true,
|
||||
bool allow_non_friend_support_unit = false);
|
||||
|
||||
protected:
|
||||
virtual bool _run() override { return true; };
|
||||
|
||||
private:
|
||||
/// <summary>
|
||||
/// 在职业 role 的助战列表中寻找一名列于 required_opers 中的干员并使用其指定技能;
|
||||
/// 若 role == Role::Unknown 则忽视 required_opers,在当前职业的助战列表中随机选择一名干员并使用其默认技能。
|
||||
/// </summary>
|
||||
/// <param name="role">所需助战干员的职业。</param>
|
||||
/// <param name="required_opers">
|
||||
/// 所需助战干员列表,当且仅当 role != Role::Unknown 时有效,默认其中的干员都属于职业 role。
|
||||
/// </param>
|
||||
/// <param name="max_refresh_times">最大刷新助战列表的次数。</param>
|
||||
/// <param name="max_spec_lvl">是否要求技能专三。</param>
|
||||
/// <param name="allow_non_friend_support_unit">是否允许使用非好友助战干员。</param>
|
||||
/// <returns>
|
||||
/// 若成功找到并使用所需的助战干员,则返回 true,反之则返回 false。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 默认已经点开助战列表;
|
||||
/// 每次只能识别一页助战列表,因此最多会识别 MaxNumSupportListPages * (max_refresh_times + 1) 次;
|
||||
/// 若识别到多个满足条件的干员,则优先选择在 required_opers 中排序靠前的干员;
|
||||
/// 当 role == Role::Unknown 时因没有指定技能,max_spec_lvl == true 仅会要求助战干员的专精等级达到 2。
|
||||
/// </remarks>
|
||||
bool try_add_support_unit_for_role(
|
||||
Role role = Role::Unknown,
|
||||
const std::vector<RequiredOper>& required_opers = {},
|
||||
int max_refresh_times = 0,
|
||||
bool max_spec_lvl = true,
|
||||
bool allow_non_friend_support_unit = false);
|
||||
|
||||
/// <summary>
|
||||
/// 点选助战干员,判断技能是否为专三,并使用其 skill 技能;
|
||||
/// 若 int == 0 则忽视 max_spec_lvl,并使用 support_unit 的默认技能。
|
||||
/// </summary>
|
||||
/// <param name="support_unit">目标助战干员。</param>
|
||||
/// <param name="skill">目标技能。</param>
|
||||
/// <param name="max_spec_lvl">是否要求技能专三。</param>
|
||||
/// <returns>
|
||||
/// 若成功使用助战干员,则返回 true,反之则返回 false。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 默认目标技能存在且已解锁。
|
||||
/// </remarks>
|
||||
bool try_use_support_unit_with_skill(const SupportUnit& support_unit, int skill = 0, bool max_spec_lvl = true);
|
||||
|
||||
/// <summary>
|
||||
/// 助战列表的总页数。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 助战列表共有 9 个栏位,一页即一屏,屏幕上最多只能同时完整显示 8 名助战干员,因而总页数为 2。
|
||||
/// 注意助战列表页之间的内容重叠,可以基于“助战列表中不会有重复名字的干员”的假设优化。
|
||||
/// </remarks>
|
||||
static constexpr int MaxNumSupportListPages = 2;
|
||||
};
|
||||
} // namespace asst
|
||||
95
src/MaaCore/Vision/Battle/SupportListAnalyzer.cpp
Normal file
95
src/MaaCore/Vision/Battle/SupportListAnalyzer.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "SupportListAnalyzer.h"
|
||||
|
||||
#include "Config/TaskData.h"
|
||||
#include "Utils/NoWarningCV.h"
|
||||
#include "Vision/Matcher.h"
|
||||
#include "Vision/MultiMatcher.h"
|
||||
#include "Vision/RegionOCRer.h"
|
||||
|
||||
bool asst::SupportListAnalyzer::analyze(const std::unordered_set<std::string>& ignored_oper_names)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
MultiMatcher flag_analyzer(m_image);
|
||||
flag_analyzer.set_task_info("SupportListAnalyzer-Flag");
|
||||
if (!flag_analyzer.analyze()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& name_task_ptr = Task.get<OcrTaskInfo>("SupportListAnalyzer-NameOcr");
|
||||
const auto& elite_task_ptr = Task.get<MatchTaskInfo>("SupportListAnalyzer-Elite");
|
||||
const auto& level_task_ptr = Task.get<OcrTaskInfo>("SupportListAnalyzer-LevelOcr");
|
||||
|
||||
RegionOCRer ocr_analyzer(m_image);
|
||||
Matcher elite_analyzer(m_image);
|
||||
|
||||
std::vector<SupportUnit> results;
|
||||
for (const auto& [rect, score, templ_name] : flag_analyzer.get_result()) {
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw, make_rect<cv::Rect>(rect), cv::Scalar(0, 255, 0), 2);
|
||||
#endif
|
||||
|
||||
// 干员名称
|
||||
const Rect name_roi = rect.move(name_task_ptr->roi);
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw, make_rect<cv::Rect>(name_roi), cv::Scalar(0, 255, 0), 2);
|
||||
#endif
|
||||
|
||||
ocr_analyzer.set_roi(name_roi);
|
||||
ocr_analyzer.set_replace(name_task_ptr->replace_map, name_task_ptr->replace_full);
|
||||
if (!ocr_analyzer.analyze()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& name = ocr_analyzer.get_result().text;
|
||||
// 如果干员名称出现在 ignored_oper_names 则忽视这个干员
|
||||
if (ignored_oper_names.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 干员精英化等级
|
||||
const Rect elite_roi = rect.move(elite_task_ptr->roi);
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw, make_rect<cv::Rect>(elite_roi), cv::Scalar(0, 255, 0), 2);
|
||||
#endif
|
||||
|
||||
elite_analyzer.set_task_info(elite_task_ptr);
|
||||
elite_analyzer.set_roi(elite_roi);
|
||||
int elite = 0;
|
||||
if (elite_analyzer.analyze()) {
|
||||
elite = (elite_analyzer.get_result().templ_name == "SupportListAnalyzer-Elite-2" ? 2 : 1);
|
||||
}
|
||||
|
||||
// 干员等级
|
||||
const Rect level_roi = rect.move(level_task_ptr->roi);
|
||||
#ifdef ASST_DEBUG
|
||||
cv::rectangle(m_image_draw, make_rect<cv::Rect>(level_roi), cv::Scalar(0, 255, 0), 2);
|
||||
#endif
|
||||
|
||||
ocr_analyzer.set_roi(level_roi);
|
||||
ocr_analyzer.set_replace(level_task_ptr->replace_map, level_task_ptr->replace_full);
|
||||
if (!ocr_analyzer.analyze()) {
|
||||
continue;
|
||||
}
|
||||
int level = 0;
|
||||
if (!utils::chars_to_number(ocr_analyzer.get_result().text, level)) {
|
||||
continue;
|
||||
}
|
||||
Log.info(ocr_analyzer.get_result().text, level);
|
||||
|
||||
SupportUnit support_unit {
|
||||
.rect = rect,
|
||||
.name = name,
|
||||
.elite = elite,
|
||||
.level = level,
|
||||
.from_friend = (templ_name == "SupportListAnalyzer-Friend.png"),
|
||||
};
|
||||
LogInfo << __FUNCTION__ << "| Found support operator from"
|
||||
<< (support_unit.from_friend ? "friend:" : "non-friend:") << support_unit.name << "with elite"
|
||||
<< support_unit.elite << "and level" << support_unit.level;
|
||||
results.emplace_back(std::move(support_unit));
|
||||
}
|
||||
|
||||
m_result = std::move(results);
|
||||
return !m_result.empty();
|
||||
}
|
||||
45
src/MaaCore/Vision/Battle/SupportListAnalyzer.h
Normal file
45
src/MaaCore/Vision/Battle/SupportListAnalyzer.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "Vision/VisionHelper.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class SupportListAnalyzer final : public VisionHelper
|
||||
{
|
||||
public:
|
||||
using VisionHelper::VisionHelper;
|
||||
virtual ~SupportListAnalyzer() noexcept override = default;
|
||||
|
||||
struct SupportUnit // 备选助战干员
|
||||
{
|
||||
Rect rect;
|
||||
std::string name; // 干员名称/代号
|
||||
int elite = 0; // 精英化阶段
|
||||
int level = 0; // 等级
|
||||
bool from_friend = false; // 是否为好友助战
|
||||
// SomeType modules // 模组
|
||||
// ———————— 以下字段仅在集成战略中有效 ————————
|
||||
int hope = 0; // 希望消耗
|
||||
int elite_after_promotion = 0; // 进阶后精英化阶段,仅在集成战略中有效,
|
||||
int level_after_promotion = 0; // 进阶后等级,仅在集成战略中有效,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 识别 m_image 中显示的助战列表页,忽视名字出现在 ignored_oper_names 中的干员。
|
||||
/// </summary>
|
||||
/// <param name="ignored_oper_names">需要被忽视的干员的名字。</param>
|
||||
/// <remarks>
|
||||
/// 助战列表共有 9 个栏位,一页即一屏,屏幕上最多只能同时完整显示 8 名助战干员。
|
||||
/// 基于“助战列表中不会有重复名字的干员”的假设,ignored_oper_names 参数用于筛除助战列表页之间的内容重叠。
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// 返回识别到的助战干员列表。
|
||||
/// </returns>
|
||||
bool analyze(const std::unordered_set<std::string>& ignored_oper_names = {});
|
||||
|
||||
[[nodiscard]] std::vector<SupportUnit> get_result() const { return m_result; };
|
||||
|
||||
private:
|
||||
std::vector<SupportUnit> m_result;
|
||||
};
|
||||
}
|
||||
Submodule src/MaaMacGui updated: 355a6f195d...88f5ed0eea
@@ -1372,6 +1372,13 @@ namespace MaaWpfGui.Main
|
||||
break;
|
||||
}
|
||||
|
||||
case "RecruitSuppportOperator":
|
||||
{
|
||||
var name = subTaskDetails!["name"]!.ToString();
|
||||
Instances.TaskQueueViewModel.AddLog(string.Format(LocalizationHelper.GetString("RecruitSuppportOperator"), name), UiLogColor.Info);
|
||||
break;
|
||||
}
|
||||
|
||||
case "RecruitTagsSelected":
|
||||
{
|
||||
var selected = subTaskDetails["tags"] ?? new JArray();
|
||||
|
||||
@@ -728,6 +728,7 @@ The video aspect ratio needs to be 16:9 without interference factors such as bla
|
||||
<system:String x:Key="GiveUpUploadingPenguins">Abort upload to Penguin Statistics</system:String>
|
||||
<system:String x:Key="TheEx">No bonus stage, stopped</system:String>
|
||||
<system:String x:Key="MissingOperators">Operators of the following operator groups are missing:</system:String>
|
||||
<system:String x:Key="RecruitSuppportOperator">Add support unit: {0}</system:String>
|
||||
<system:String x:Key="StageQueue">Stage Queue:</system:String>
|
||||
<system:String x:Key="UnableToAgent">Unable to use PRTS</system:String>
|
||||
<system:String x:Key="MissionStart">Mission started</system:String>
|
||||
|
||||
@@ -731,6 +731,7 @@ C:\\leidian\\LDPlayer9
|
||||
<system:String x:Key="GiveUpUploadingPenguins">Penguin-Statsへのアップロードをあきらめました</system:String>
|
||||
<system:String x:Key="TheEx">ステージ報酬がないため停止します</system:String>
|
||||
<system:String x:Key="MissingOperators">次のオペレーターがオペレーターグループにありません:</system:String>
|
||||
<system:String x:Key="RecruitSuppportOperator">サポートユニットを追加:{0}</system:String>
|
||||
<system:String x:Key="StageQueue">ステージキュー:</system:String>
|
||||
<system:String x:Key="UnableToAgent">自動指揮利用不可</system:String>
|
||||
<system:String x:Key="MissionStart">行動開始しました</system:String>
|
||||
|
||||
@@ -731,6 +731,7 @@ C:\\leidian\\LDPlayer9
|
||||
<system:String x:Key="GiveUpUploadingPenguins">펭귄 물류에 업로드 불가</system:String>
|
||||
<system:String x:Key="TheEx">EX 스테이지가 없어 중단되었습니다</system:String>
|
||||
<system:String x:Key="MissingOperators">다음 오퍼레이터들이 누락되었습니다:</system:String>
|
||||
<system:String x:Key="RecruitSuppportOperator">지원 유닛 추가: {0}</system:String>
|
||||
<system:String x:Key="StageQueue">스테이지 대기열:</system:String>
|
||||
<system:String x:Key="UnableToAgent">프록시 명령을 사용할 수 없습니다</system:String>
|
||||
<system:String x:Key="MissionStart">행동 개시</system:String>
|
||||
|
||||
@@ -730,6 +730,7 @@ C:\\leidian\\LDPlayer9。\n
|
||||
<system:String x:Key="GiveUpUploadingPenguins">放弃上传企鹅物流</system:String>
|
||||
<system:String x:Key="TheEx">无奖励关卡,已停止</system:String>
|
||||
<system:String x:Key="MissingOperators">缺少以下干员组中的干员:</system:String>
|
||||
<system:String x:Key="RecruitSuppportOperator">编入助战干员:{0}</system:String>
|
||||
<system:String x:Key="StageQueue">关卡队列:</system:String>
|
||||
<system:String x:Key="UnableToAgent">无法使用代理指挥</system:String>
|
||||
<system:String x:Key="MissionStart">已开始行动</system:String>
|
||||
|
||||
@@ -730,6 +730,7 @@ C:\\leidian\\LDPlayer9。\n
|
||||
<system:String x:Key="GiveUpUploadingPenguins">放棄上傳企鵝物流</system:String>
|
||||
<system:String x:Key="TheEx">無獎勵關卡,已停止</system:String>
|
||||
<system:String x:Key="MissingOperators">缺少以下幹員組的幹員:</system:String>
|
||||
<system:String x:Key="RecruitSuppportOperator">編入助戰幹員:{0}</system:String>
|
||||
<system:String x:Key="StageQueue">關卡隊列:</system:String>
|
||||
<system:String x:Key="UnableToAgent">無法使用代理指揮</system:String>
|
||||
<system:String x:Key="MissionStart">已開始行動</system:String>
|
||||
|
||||
Reference in New Issue
Block a user