mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-17 18:01:26 +08:00
359 lines
13 KiB
C++
359 lines
13 KiB
C++
#include "RoguelikeRecruitTaskPlugin.h"
|
||
|
||
#include "Controller.h"
|
||
#include "Logger.hpp"
|
||
#include "ProcessTask.h"
|
||
#include "RoguelikeRecruitConfiger.h"
|
||
#include "RoguelikeRecruitImageAnalyzer.h"
|
||
#include "RuntimeStatus.h"
|
||
#include "TaskData.h"
|
||
|
||
bool asst::RoguelikeRecruitTaskPlugin::verify(AsstMsg msg, const json::value& details) const
|
||
{
|
||
if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") {
|
||
return false;
|
||
}
|
||
|
||
if (details.at("details").at("task").as_string() == "Roguelike1ChooseOper") {
|
||
return true;
|
||
}
|
||
else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool asst::RoguelikeRecruitTaskPlugin::_run()
|
||
{
|
||
LogTraceFunction;
|
||
|
||
if (check_core_char()) {
|
||
return true;
|
||
}
|
||
|
||
bool recruited = false;
|
||
|
||
auto recruit_oper = [&](const BattleRecruitOperInfo& info) {
|
||
select_oper(info);
|
||
recruited = true;
|
||
};
|
||
|
||
// 编队信息 (已有角色)
|
||
std::string str_chars_info =
|
||
m_status->get_str(RuntimeStatus::RoguelikeCharOverview).value_or(json::value().to_string());
|
||
json::value json_chars_info = json::parse(str_chars_info).value_or(json::value());
|
||
const auto& chars_map = json_chars_info.as_object();
|
||
|
||
// 候选干员
|
||
std::vector<RoguelikeRecruitInfo> recruit_list;
|
||
|
||
// 干员名字的识别位置
|
||
std::unordered_map<std::string, Rect> last_oper_rects;
|
||
|
||
constexpr int SwipeTimes = 5;
|
||
int i = 0;
|
||
|
||
// 翻页找出所有候选干员
|
||
for (; i != SwipeTimes; ++i) {
|
||
auto image = m_ctrler->get_image();
|
||
RoguelikeRecruitImageAnalyzer analyzer(image);
|
||
if (!analyzer.analyze()) {
|
||
Log.trace(__FUNCTION__, "| Page", i, "recruit list analyse failed");
|
||
// 还没滑动就识别失败,通常是招募界面为空,视为招募成功并退出
|
||
if (i == 0) {
|
||
return true;
|
||
}
|
||
// 已经滑动过,识别失败可能是干员不可选
|
||
break;
|
||
}
|
||
|
||
std::string oper_names = "";
|
||
const auto& oper_list = analyzer.get_result();
|
||
bool stop_swipe = false;
|
||
|
||
for (const auto& oper_info : oper_list) {
|
||
if (!oper_names.empty()) {
|
||
oper_names += ", ";
|
||
}
|
||
oper_names += oper_info.name;
|
||
|
||
// 查询上次识别位置
|
||
const auto& rect_it = last_oper_rects.find(oper_info.name);
|
||
if (rect_it == last_oper_rects.cend()) {
|
||
last_oper_rects.emplace(oper_info.name, oper_info.rect);
|
||
}
|
||
else {
|
||
const auto x_offset = abs(rect_it->second.x - oper_info.rect.x);
|
||
const auto y_offset = abs(rect_it->second.y - oper_info.rect.y);
|
||
|
||
// 偏移低于阈值,代表划动没有效果,已经到达屏幕最右侧
|
||
if (x_offset < 20 && y_offset < 5) {
|
||
stop_swipe = true;
|
||
Log.trace(__FUNCTION__, "| Page", i, "oper", oper_info.name,
|
||
"last rect:", rect_it->second.to_string(), "current rect:", oper_info.rect.to_string(),
|
||
" - stop swiping");
|
||
break;
|
||
}
|
||
|
||
rect_it->second = oper_info.rect;
|
||
}
|
||
|
||
// 查询招募配置
|
||
auto& recruit_info = RoguelikeRecruit.get_oper_info(oper_info.name);
|
||
if (recruit_info.name.empty()) {
|
||
continue;
|
||
}
|
||
|
||
int priority = 0;
|
||
|
||
// 查询编队是否已持有该干员
|
||
const auto& char_opt = chars_map.find(oper_info.name);
|
||
|
||
if (char_opt.has_value()) {
|
||
// 干员已在编队中,又出现在招募列表,只有待晋升和预备干员两种情况
|
||
if (recruit_info.is_alternate) {
|
||
// 预备干员可以重复招募
|
||
priority = recruit_info.recruit_priority;
|
||
}
|
||
else {
|
||
// 干员待晋升
|
||
priority = recruit_info.promote_priority;
|
||
}
|
||
}
|
||
else {
|
||
// 干员待招募,检查练度
|
||
if (oper_info.elite == 2) {
|
||
// TODO: 招募时已精二 (随机提升或对应分队) => promoted_priority
|
||
priority = recruit_info.recruit_priority;
|
||
}
|
||
else if (oper_info.elite == 1 && oper_info.level >= 50) {
|
||
// 精一50级以上
|
||
priority = recruit_info.recruit_priority;
|
||
}
|
||
else {
|
||
// 精一50级以下,默认不招募
|
||
Log.trace(__FUNCTION__, "| Ignored low level oper:", oper_info.name, oper_info.elite,
|
||
oper_info.level);
|
||
}
|
||
}
|
||
|
||
// 优先级为0,可能练度不够被忽略
|
||
if (priority == 0) {
|
||
continue;
|
||
}
|
||
|
||
// 添加到候选名单
|
||
auto existing_it = ranges::find_if(
|
||
recruit_list, [&](const RoguelikeRecruitInfo& pri) -> bool { return pri.name == recruit_info.name; });
|
||
if (existing_it == recruit_list.cend()) {
|
||
RoguelikeRecruitInfo info;
|
||
info.name = recruit_info.name;
|
||
info.priority = priority;
|
||
info.is_alternate = recruit_info.is_alternate;
|
||
info.page_index = i;
|
||
recruit_list.emplace_back(info);
|
||
}
|
||
}
|
||
|
||
Log.info(__FUNCTION__, "| Page", i, "oper list:", oper_names);
|
||
|
||
if (stop_swipe) {
|
||
break;
|
||
}
|
||
|
||
// 每列4个干员,未滑动时可以显示2列,滑动后至少可以显示1列
|
||
const auto oper_count = oper_list.size();
|
||
if ((i == 0 && oper_count < 8) || oper_count < 4) {
|
||
Log.trace(__FUNCTION__, "| Page", i, "oper count:", oper_count, "- stop swiping");
|
||
break;
|
||
}
|
||
|
||
// 向右滑动
|
||
Log.trace(__FUNCTION__, "| Page", i, "oper count:", oper_count, "- continue swiping");
|
||
slowly_swipe(ProcessTaskAction::SlowlySwipeToTheRight);
|
||
sleep(Task.get("Roguelike1Custom-HijackSquad")->rear_delay);
|
||
}
|
||
|
||
// 没有候选干员,进入后备逻辑
|
||
if (recruit_list.empty()) {
|
||
Log.trace(__FUNCTION__, "| No oper in recruit list.");
|
||
|
||
// 如果划动过,先划回最左边
|
||
if (i != 0) {
|
||
swipe_to_the_left_of_operlist(i / 2 + 1);
|
||
}
|
||
|
||
auto image = m_ctrler->get_image();
|
||
RoguelikeRecruitImageAnalyzer analyzer(image);
|
||
if (!analyzer.analyze()) {
|
||
Log.error(__FUNCTION__, "| Random recruitment analyse failed");
|
||
return false;
|
||
}
|
||
|
||
const auto& oper_list = analyzer.get_result();
|
||
|
||
// 可能是晋升界面,随便选个精二的
|
||
for (const auto& info : oper_list) {
|
||
if (info.elite != 2) {
|
||
continue;
|
||
}
|
||
Log.trace(__FUNCTION__, "| Choose random elite 2:", info.name, info.elite, info.level);
|
||
recruit_oper(info);
|
||
return true;
|
||
}
|
||
|
||
// 仍然没选,随便选个精一 50 以上的
|
||
for (const auto& info : oper_list) {
|
||
if (info.elite == 0 || (info.elite == 1 && info.level < 50)) {
|
||
continue;
|
||
}
|
||
Log.trace(__FUNCTION__, "| Choose random elite 1:", info.name, info.elite, info.level);
|
||
recruit_oper(info);
|
||
return true;
|
||
}
|
||
|
||
Log.trace(__FUNCTION__, "| Did not choose oper");
|
||
return false;
|
||
}
|
||
|
||
// 选择优先级最高的干员
|
||
auto selected_oper = ranges::max_element(recruit_list, std::less {}, std::mem_fn(&RoguelikeRecruitInfo::priority));
|
||
if (selected_oper == recruit_list.cend()) {
|
||
Log.trace(__FUNCTION__, "| No opers in recruit list.");
|
||
return false;
|
||
}
|
||
|
||
auto& char_name = selected_oper->name;
|
||
Log.trace(__FUNCTION__, "| Top priority oper:", char_name, selected_oper->priority, "page",
|
||
selected_oper->page_index, "/", i);
|
||
|
||
// 滑动方向
|
||
// 页码大于一半: 从右往左划动
|
||
// 页码小于一半: 划回最左边再往右
|
||
bool is_rtl = false;
|
||
|
||
// 如果划动过,判断目标角色离哪个方向更近
|
||
if (i != 0) {
|
||
is_rtl = (selected_oper->page_index * 2) >= i;
|
||
if (!is_rtl) {
|
||
// 从左往右需要先划回最左边
|
||
swipe_to_the_left_of_operlist(i / 2 + 1);
|
||
}
|
||
}
|
||
|
||
return check_char(char_name, is_rtl);
|
||
}
|
||
|
||
bool asst::RoguelikeRecruitTaskPlugin::check_char(const std::string& char_name, bool is_rtl)
|
||
{
|
||
LogTraceFunction;
|
||
|
||
constexpr int SwipeTimes = 10;
|
||
int i = 0;
|
||
for (; i != SwipeTimes; ++i) {
|
||
auto image = m_ctrler->get_image();
|
||
RoguelikeRecruitImageAnalyzer analyzer(image);
|
||
|
||
// 只处理识别成功的情况,失败(无任何结果)时继续滑动
|
||
if (analyzer.analyze()) {
|
||
const auto& chars = analyzer.get_result();
|
||
auto it = ranges::find_if(
|
||
chars, [&](const BattleRecruitOperInfo& oper) -> bool { return oper.name == char_name; });
|
||
|
||
std::string oper_names = "";
|
||
for (const auto& oper : chars) {
|
||
if (!oper_names.empty()) {
|
||
oper_names += ", ";
|
||
}
|
||
oper_names += oper.name;
|
||
}
|
||
Log.info(__FUNCTION__, "| Oper list:", oper_names);
|
||
|
||
if (it != chars.cend()) {
|
||
select_oper(*it);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// 没识别到目标干员,可能不在这一页,继续划动
|
||
if (is_rtl) {
|
||
slowly_swipe(ProcessTaskAction::SlowlySwipeToTheLeft);
|
||
}
|
||
else {
|
||
slowly_swipe(ProcessTaskAction::SlowlySwipeToTheRight);
|
||
}
|
||
sleep(Task.get("Roguelike1Custom-HijackSquad")->rear_delay);
|
||
}
|
||
Log.info(__FUNCTION__, "| Cannot find oper `" + char_name + "`");
|
||
swipe_to_the_left_of_operlist();
|
||
return false;
|
||
}
|
||
|
||
bool asst::RoguelikeRecruitTaskPlugin::check_core_char()
|
||
{
|
||
LogTraceFunction;
|
||
|
||
auto core_opt = m_status->get_str("RoguelikeCoreChar");
|
||
if (!core_opt || core_opt->empty()) {
|
||
return false;
|
||
}
|
||
m_status->set_str("RoguelikeCoreChar", "");
|
||
return check_char(core_opt.value());
|
||
}
|
||
|
||
void asst::RoguelikeRecruitTaskPlugin::select_oper(const BattleRecruitOperInfo& oper)
|
||
{
|
||
Log.info(__FUNCTION__, "| Choose oper:", oper.name, "( elite", oper.elite, "level", oper.level, ")");
|
||
|
||
m_ctrler->click(oper.rect);
|
||
|
||
m_status->set_number(RuntimeStatus::RoguelikeCharElitePrefix + oper.name, oper.elite);
|
||
m_status->set_number(RuntimeStatus::RoguelikeCharLevelPrefix + oper.name, oper.level);
|
||
|
||
std::string overview_str =
|
||
m_status->get_str(RuntimeStatus::RoguelikeCharOverview).value_or(json::value().to_string());
|
||
json::value overview = json::parse(overview_str).value_or(json::value());
|
||
overview[oper.name] = json::object {
|
||
{ "elite", oper.elite },
|
||
{ "level", oper.level },
|
||
};
|
||
m_status->set_str(RuntimeStatus::RoguelikeCharOverview, overview.to_string());
|
||
}
|
||
|
||
void asst::RoguelikeRecruitTaskPlugin::swipe_to_the_left_of_operlist(int loop_times)
|
||
{
|
||
LogTraceFunction;
|
||
static Rect begin_rect = Task.get("RoguelikeRecruitSwipeToTheLeftBegin")->specific_rect; // 1080, 200, 100, 300
|
||
static Rect end_rect = Task.get("RoguelikeRecruitSwipeToTheLeftEnd")->specific_rect; // 400, 200, 100, 300
|
||
static int duration = Task.get("RoguelikeRecruitSwipeToTheLeftBegin")->pre_delay;
|
||
static int extra_delay = Task.get("RoguelikeRecruitSwipeToTheLeftBegin")->rear_delay;
|
||
static int cfg_loop_times = Task.get("RoguelikeRecruitSwipeToTheLeftBegin")->max_times;
|
||
|
||
for (int i = 0; i != cfg_loop_times * loop_times; ++i) {
|
||
if (need_exit()) {
|
||
return;
|
||
}
|
||
m_ctrler->swipe(end_rect, begin_rect, duration, true, 0, false);
|
||
}
|
||
sleep(extra_delay);
|
||
}
|
||
|
||
void asst::RoguelikeRecruitTaskPlugin::slowly_swipe(ProcessTaskAction action)
|
||
{
|
||
LogTraceFunction;
|
||
static Rect right_rect = Task.get("RoguelikeRecruitSlowlySwipeRightRect")->specific_rect; // 780, 200, 100, 300
|
||
static Rect left_rect = Task.get("RoguelikeRecruitSlowlySwipeLeftRect")->specific_rect; // 360, 200, 100, 300
|
||
static int duration = Task.get("RoguelikeRecruitSlowlySwipeRightRect")->pre_delay;
|
||
static int extra_delay = Task.get("RoguelikeRecruitSlowlySwipeRightRect")->rear_delay;
|
||
|
||
switch (action) {
|
||
case asst::ProcessTaskAction::SlowlySwipeToTheLeft:
|
||
m_ctrler->swipe(left_rect, right_rect, duration, true, extra_delay, true);
|
||
break;
|
||
case asst::ProcessTaskAction::SlowlySwipeToTheRight:
|
||
m_ctrler->swipe(right_rect, left_rect, duration, true, extra_delay, true);
|
||
break;
|
||
default: // 走不到这里
|
||
break;
|
||
}
|
||
}
|