feat: 自动编队允许指定助战单位 (#10820)

* feat: 自动编队自动借助战

Revert d78fc68

fix: 招募助战后继续(缺少点击快速招募)

i18n: 翻译

style: clang-format

fix: basetask template

fix: werror

perf: 注释、format

i18n: en

perf: 完成功能实现,复用task,进行gui输出

perf: 更改默认状态

feat: BattleFormationTask

Revert 05baeb4 (Back to  739aa03)

* 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:
晓丶梦丶仁
2024-11-05 14:09:25 +08:00
committed by GitHub
parent 18e574d5eb
commit e595aa021f
45 changed files with 754 additions and 23 deletions

View File

@@ -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";
}
}

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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;

View File

@@ -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" }

View File

@@ -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

View File

@@ -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);

View 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;
}

View 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

View 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();
}

View 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;
};
}

View File

@@ -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();

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>