mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-14 00:40:27 +08:00
Compare commits
4 Commits
release.al
...
release.al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb6eb55161 | ||
|
|
a195f559b8 | ||
|
|
b5776d33b0 | ||
|
|
63aa3575c6 |
@@ -10,6 +10,10 @@
|
||||
#include "AsstDef.h"
|
||||
#include "Configer.h"
|
||||
|
||||
namespace cv {
|
||||
class Mat;
|
||||
}
|
||||
|
||||
namespace asst {
|
||||
class WinMacro;
|
||||
class Identify;
|
||||
@@ -20,9 +24,9 @@ namespace asst {
|
||||
Assistance();
|
||||
~Assistance();
|
||||
|
||||
std::optional<std::string> set_emulator(const std::string & emulator_name = std::string());
|
||||
|
||||
void start(const std::string & task);
|
||||
std::optional<std::string> set_emulator(const std::string& emulator_name = std::string());
|
||||
|
||||
void start(const std::string& task);
|
||||
void stop(bool block = true);
|
||||
|
||||
bool set_param(const std::string& type, const std::string& param, const std::string& value);
|
||||
@@ -32,6 +36,9 @@ namespace asst {
|
||||
private:
|
||||
static void working_proc(Assistance* pThis);
|
||||
|
||||
// pair<scale, image>
|
||||
std::pair<double, cv::Mat> get_format_image();
|
||||
|
||||
std::shared_ptr<WinMacro> m_pWindow = nullptr;
|
||||
std::shared_ptr<WinMacro> m_pView = nullptr;
|
||||
std::shared_ptr<WinMacro> m_pCtrl = nullptr;
|
||||
|
||||
@@ -108,6 +108,10 @@ namespace asst {
|
||||
std::string path;
|
||||
std::string connect;
|
||||
std::string click;
|
||||
std::string display;
|
||||
std::string display_regex;
|
||||
int display_width = 0;
|
||||
int display_height = 0;
|
||||
};
|
||||
|
||||
struct EmulatorInfo {
|
||||
@@ -121,29 +125,31 @@ namespace asst {
|
||||
int height = 0;
|
||||
int x_offset = 0;
|
||||
int y_offset = 0;
|
||||
int right_offset = 0;
|
||||
int bottom_offset = 0;
|
||||
};
|
||||
|
||||
struct TaskInfo {
|
||||
std::string filename;
|
||||
double threshold = 0;
|
||||
double cache_threshold = 0;
|
||||
TaskType type = TaskType::Invalid;
|
||||
std::vector<std::string> next;
|
||||
int exec_times = 0;
|
||||
int max_times = INT_MAX;
|
||||
std::vector<std::string> exceeded_next;
|
||||
std::vector<std::string> reduce_other_times;
|
||||
asst::Rect specific_area;
|
||||
int pre_delay = 0;
|
||||
int rear_delay = 0;
|
||||
std::string filename; // 图片文件名
|
||||
double threshold = 0; // 模板匹配阈值
|
||||
double cache_threshold = 0; // 直方图比较阈值
|
||||
TaskType type = TaskType::Invalid; // 任务类型
|
||||
std::vector<std::string> next; // 下一个可能的任务(列表)
|
||||
int exec_times = 0; // 任务已执行了多少次
|
||||
int max_times = INT_MAX; // 任务最多执行多少次
|
||||
std::vector<std::string> exceeded_next; // 达到最多次数了之后,下一个可能的任务(列表)
|
||||
std::vector<std::string> reduce_other_times; // 执行了该任务后,需要减少别的任务的执行次数。例如执行了吃理智药,则说明上一次点击蓝色开始行动按钮没生效,所以蓝色开始行动要-1
|
||||
asst::Rect specific_area; // 指定区域,目前仅针对ClickRect任务有用,会点这个区域
|
||||
int pre_delay = 0; // 执行该任务前的延时
|
||||
int rear_delay = 0; // 执行该任务后的延时
|
||||
};
|
||||
|
||||
struct Options {
|
||||
int identify_delay = 0;
|
||||
bool identify_cache = false;
|
||||
int control_delay_lower = 0;
|
||||
int control_delay_upper = 0;
|
||||
bool print_window = false;
|
||||
int print_window_delay = 0;
|
||||
bool identify_cache = false; // 图像识别缓存功能:开启后可以大幅降低CPU消耗,但需要保证要识别的按钮每次的位置不会改变
|
||||
int identify_delay = 0; // 图像识别延时:越快操作越快,但会增加CPU消耗
|
||||
int control_delay_lower = 0; // 点击随机延时下限:每次点击操作会进行随机延时
|
||||
int control_delay_upper = 0; // 点击随机延时上限:每次点击操作会进行随机延时
|
||||
bool print_window = false; // 截图功能:开启后每次结算界面会截图到screenshot目录下
|
||||
int print_window_delay = 0; // 截图延时:每次到结算界面,掉落物品不是一次性出来的,有个动画,所以需要等一会再截图
|
||||
};
|
||||
}
|
||||
@@ -27,8 +27,8 @@ namespace asst {
|
||||
Configer& operator=(const Configer& rhs);
|
||||
Configer& operator=(Configer&& rhs) noexcept;
|
||||
|
||||
constexpr static int DefaultWindowWidth = 1280;
|
||||
constexpr static int DefaultWindowHeight = 720;
|
||||
constexpr static int DefaultWindowWidth = 1271;
|
||||
constexpr static int DefaultWindowHeight = 716;
|
||||
constexpr static double DefaultThreshold = 0.9;
|
||||
constexpr static double DefaultCacheThreshold = 0.9;
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ namespace asst {
|
||||
|
||||
void clear_cache();
|
||||
private:
|
||||
cv::Mat image2hist(const cv::Mat& src);
|
||||
cv::Mat image_2_hist(const cv::Mat& src);
|
||||
double image_hist_comp(const cv::Mat& src, const cv::MatND& hist);
|
||||
static asst::Rect cvrect2rect(const cv::Rect& cvRect) {
|
||||
static asst::Rect cvrect_2_rect(const cv::Rect& cvRect) {
|
||||
return asst::Rect(cvRect.x, cvRect.y, cvRect.width, cvRect.height);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace asst {
|
||||
|
||||
std::unordered_map<std::string, cv::Mat> m_mat_map;
|
||||
bool m_use_cache = true;
|
||||
std::unordered_map<std::string, std::pair<cv::Rect, cv::Mat>> m_cache_map;
|
||||
std::unordered_map<std::string, std::pair<cv::Rect, cv::Mat>> m_cache_map; // λÖá¢Ö±·½Í¼»º´æ
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "AsstAux.h"
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
#include <string>
|
||||
#include <random>
|
||||
#include <optional>
|
||||
#include <Windows.h>
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#include "AsstDef.h"
|
||||
|
||||
namespace cv {
|
||||
class Mat;
|
||||
}
|
||||
|
||||
namespace asst {
|
||||
class WinMacro
|
||||
{
|
||||
@@ -21,6 +25,7 @@ namespace asst {
|
||||
bool hideWindow();
|
||||
bool click(const Point & p);
|
||||
bool click(const Rect & rect);
|
||||
void setControlScale(double scale);
|
||||
cv::Mat getImage(const Rect& rect);
|
||||
Rect getWindowRect();
|
||||
const EmulatorInfo& getEmulatorInfo() const noexcept { return m_emulator_info; }
|
||||
@@ -29,17 +34,18 @@ namespace asst {
|
||||
static double getScreenScale();
|
||||
private:
|
||||
bool findHandle();
|
||||
DWORD callCmd(const std::string& cmd, int wait_time = 1000);
|
||||
std::optional<std::string> callCmd(const std::string& cmd, bool use_pipe = true);
|
||||
|
||||
const EmulatorInfo m_emulator_info;
|
||||
const HandleType m_handle_type;
|
||||
HWND m_handle = NULL;
|
||||
bool m_is_adb = false;
|
||||
std::string m_click_cmd;
|
||||
std::string m_click_cmd; // adb点击命令,不是adb的句柄用不到这个
|
||||
std::minstd_rand m_rand_engine;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
int m_x_offset = 0;
|
||||
int m_y_offset = 0;
|
||||
//int m_x_offset = 0;
|
||||
//int m_y_offset = 0;
|
||||
double m_control_scale = 1;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "Logger.hpp"
|
||||
#include "AsstAux.h"
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#include <time.h>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -62,6 +64,7 @@ std::optional<std::string> Assistance::set_emulator(const std::string& emulator_
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
// 自动匹配模拟器,逐个找
|
||||
if (emulator_name.empty()) {
|
||||
for (auto&& [name, info] : m_configer.m_handles)
|
||||
{
|
||||
@@ -147,19 +150,8 @@ bool asst::Assistance::print_window(const std::string& filename, bool block)
|
||||
lock = std::unique_lock<std::mutex>(m_mutex);
|
||||
}
|
||||
|
||||
auto cur_image = m_pView->getImage(m_pView->getWindowRect());
|
||||
if (cur_image.empty() || cur_image.cols < m_configer.DefaultWindowWidth || cur_image.rows < m_configer.DefaultWindowHeight) {
|
||||
DebugTraceError("Window image error");
|
||||
return false;
|
||||
}
|
||||
// 把模拟器边框的一圈裁剪掉,不然企鹅物流识别不出来
|
||||
auto&& window_info = m_pView->getEmulatorInfo();
|
||||
int x_offset = -window_info.x_offset + 5;
|
||||
int y_offset = -window_info.y_offset;// + 5;
|
||||
int width = m_configer.DefaultWindowWidth - 5;
|
||||
int height = m_configer.DefaultWindowHeight - 5;
|
||||
cv::Mat resize_mat( cur_image, cv::Rect(x_offset, y_offset, width, height));
|
||||
bool ret = cv::imwrite(filename.c_str(), resize_mat);
|
||||
auto&& [scale, image] = get_format_image();
|
||||
bool ret = cv::imwrite(filename.c_str(), image);
|
||||
|
||||
if (ret) {
|
||||
DebugTraceInfo("PrintWindow to", filename);
|
||||
@@ -177,14 +169,15 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
while (!pThis->m_thread_exit) {
|
||||
std::unique_lock<std::mutex> lock(pThis->m_mutex);
|
||||
if (pThis->m_thread_running) {
|
||||
auto cur_image = pThis->m_pView->getImage(pThis->m_pView->getWindowRect());
|
||||
auto && [scale, cur_image] = pThis->get_format_image();
|
||||
pThis->m_pCtrl->setControlScale(scale);
|
||||
|
||||
if (cur_image.empty()) {
|
||||
DebugTraceError("Unable to capture window image!!!");
|
||||
pThis->stop(false);
|
||||
continue;
|
||||
}
|
||||
if (cur_image.cols < pThis->m_configer.DefaultWindowWidth || cur_image.rows < pThis->m_configer.DefaultWindowHeight) {
|
||||
if (cur_image.rows < 100) {
|
||||
DebugTraceInfo("Window Could not be minimized!!!");
|
||||
pThis->m_pWindow->showWindow();
|
||||
pThis->m_condvar.wait_for(lock,
|
||||
@@ -195,7 +188,7 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
|
||||
std::string matched_task;
|
||||
Rect matched_rect;
|
||||
|
||||
// 逐个匹配当前可能的图像
|
||||
for (auto&& task_name : pThis->m_next_tasks) {
|
||||
|
||||
double threshold = pThis->m_configer.m_tasks[task_name].threshold;
|
||||
@@ -212,9 +205,11 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
}
|
||||
}
|
||||
|
||||
// 执行任务
|
||||
if (!matched_task.empty()) {
|
||||
auto&& task = pThis->m_configer.m_tasks[matched_task];
|
||||
DebugTraceInfo("***Matched***", matched_task, "Type:", task.type);
|
||||
// 前置固定延时
|
||||
if (task.pre_delay > 0) {
|
||||
DebugTrace("PreDelay", task.pre_delay);
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(task.pre_delay));
|
||||
@@ -227,6 +222,7 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
DebugTrace("CurTimes:", task.exec_times, "MaxTimes:", task.max_times);
|
||||
}
|
||||
if (task.exec_times < task.max_times) {
|
||||
// 随机延时功能
|
||||
if ((task.type & TaskType::BasicClick)
|
||||
&& pThis->m_configer.m_options.control_delay_upper != 0) {
|
||||
static std::default_random_engine rand_engine(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
@@ -237,6 +233,7 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
[&]() -> bool { return !pThis->m_thread_running; });
|
||||
if (cv_ret) { continue; }
|
||||
}
|
||||
|
||||
switch (task.type) {
|
||||
case TaskType::ClickRect:
|
||||
matched_rect = task.specific_area;
|
||||
@@ -256,6 +253,7 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
break;
|
||||
case TaskType::PrintWindow:
|
||||
if (pThis->m_configer.m_options.print_window) {
|
||||
// 每次到结算界面,掉落物品不是一次性出来的,有个动画,所以需要等一会再截图
|
||||
int print_delay = pThis->m_configer.m_options.print_window_delay;
|
||||
DebugTraceInfo("Ready to print window, delay", print_delay);
|
||||
pThis->m_condvar.wait_for(lock,
|
||||
@@ -274,10 +272,15 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
break;
|
||||
}
|
||||
++task.exec_times;
|
||||
|
||||
// 减少其他任务的执行次数
|
||||
// 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效
|
||||
// 所以要给蓝色开始行动的次数减一
|
||||
for (auto&& reduce : task.reduce_other_times) {
|
||||
--pThis->m_configer.m_tasks[reduce].exec_times;
|
||||
DebugTrace("Reduce exec times", reduce, pThis->m_configer.m_tasks[reduce].exec_times);
|
||||
}
|
||||
// 后置固定延时
|
||||
if (task.rear_delay > 0) {
|
||||
DebugTrace("RearDelay", task.rear_delay);
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(task.rear_delay));
|
||||
@@ -292,6 +295,7 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
pThis->m_next_tasks = pThis->m_configer.m_tasks[matched_task].exceeded_next;
|
||||
}
|
||||
|
||||
// 单纯为了打印日志。。感觉可以优化下
|
||||
std::string nexts_str;
|
||||
for (auto&& name : pThis->m_next_tasks) {
|
||||
nexts_str += name + ",";
|
||||
@@ -301,6 +305,7 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
}
|
||||
DebugTrace("Next:", nexts_str);
|
||||
}
|
||||
|
||||
pThis->m_condvar.wait_for(lock,
|
||||
std::chrono::milliseconds(pThis->m_configer.m_options.identify_delay),
|
||||
[&]() -> bool { return !pThis->m_thread_running; });
|
||||
@@ -310,3 +315,28 @@ void Assistance::working_proc(Assistance* pThis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<double, cv::Mat> asst::Assistance::get_format_image()
|
||||
{
|
||||
auto && cur_image = m_pView->getImage(m_pView->getWindowRect());
|
||||
if (cur_image.empty() || cur_image.rows < 100) {
|
||||
DebugTraceError("Window image error");
|
||||
return { 0, cur_image };
|
||||
}
|
||||
// 把模拟器边框的一圈裁剪掉
|
||||
auto&& window_info = m_pView->getEmulatorInfo();
|
||||
int x_offset = window_info.x_offset;
|
||||
int y_offset = window_info.y_offset;
|
||||
int width = cur_image.cols - x_offset - window_info.right_offset;
|
||||
int height = cur_image.rows - y_offset - window_info.bottom_offset;
|
||||
|
||||
cv::Mat cropped(cur_image, cv::Rect(x_offset, y_offset, width, height));
|
||||
|
||||
// 调整尺寸,与资源中截图的标准尺寸一致
|
||||
cv::Mat dst;
|
||||
cv::resize(cropped, dst, cv::Size(m_configer.DefaultWindowWidth, m_configer.DefaultWindowHeight));
|
||||
|
||||
double scale = static_cast<double>(width) / m_configer.DefaultWindowWidth;
|
||||
|
||||
return { scale, dst };
|
||||
}
|
||||
|
||||
@@ -162,17 +162,25 @@ bool Configer::reload(const std::string& filename)
|
||||
handle_info.window_name = info["window"].as_string();
|
||||
emulator_info.control.emplace_back(handle_info);
|
||||
}
|
||||
if (emulator_json.exist("adbControl")) {
|
||||
if (emulator_json.exist("adb")) {
|
||||
emulator_info.is_adb = true;
|
||||
// meojson的bug,暂时没空修,先转个字符串
|
||||
emulator_info.adb.path = StringReplaceAll(emulator_json["adbControl"]["path"].as_string(), "\\\\", "\\");
|
||||
emulator_info.adb.connect = emulator_json["adbControl"]["connect"].as_string();
|
||||
emulator_info.adb.click = emulator_json["adbControl"]["click"].as_string();
|
||||
emulator_info.adb.path = StringReplaceAll(emulator_json["adb"]["path"].as_string(), "\\\\", "\\");
|
||||
emulator_info.adb.connect = emulator_json["adb"]["connect"].as_string();
|
||||
emulator_info.adb.click = emulator_json["adb"]["click"].as_string();
|
||||
emulator_info.adb.display = emulator_json["adb"]["display"].as_string();
|
||||
emulator_info.adb.display_regex = emulator_json["adb"]["displayRegex"].as_string();
|
||||
}
|
||||
emulator_info.width = emulator_json["width"].as_integer();
|
||||
emulator_info.height = emulator_json["height"].as_integer();
|
||||
emulator_info.x_offset = emulator_json["xOffset"].as_integer();
|
||||
emulator_info.y_offset = emulator_json["yOffset"].as_integer();
|
||||
if (emulator_json.exist("rightOffset")) {
|
||||
emulator_info.right_offset = emulator_json["rightOffset"].as_integer();
|
||||
}
|
||||
if (emulator_json.exist("bottomOffset")) {
|
||||
emulator_info.bottom_offset = emulator_json["bottomOffset"].as_integer();
|
||||
}
|
||||
|
||||
temp.m_handles.emplace(name, emulator_info);
|
||||
}
|
||||
@@ -189,6 +197,7 @@ bool Configer::reload(const std::string& filename)
|
||||
|
||||
bool Configer::set_param(const std::string& type, const std::string& param, const std::string& value)
|
||||
{
|
||||
// 暂时只用到了这些,总的参数太多了,后面要用啥再加上
|
||||
if (type == "task.type") {
|
||||
if (m_tasks.find(param) == m_tasks.cend()) {
|
||||
return false;
|
||||
@@ -227,6 +236,7 @@ bool Configer::set_param(const std::string& type, const std::string& param, cons
|
||||
|
||||
std::optional<std::string> Configer::get_param(const std::string& type, const std::string& param)
|
||||
{
|
||||
// 暂时只用到了这些,总的参数太多了,后面要用啥再加上
|
||||
if (type == "task.execTimes" && m_tasks.find(param) != m_tasks.cend()) {
|
||||
return std::to_string(m_tasks.at(param).exec_times);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ void Identify::set_use_cache(bool b) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
Mat Identify::image2hist(const cv::Mat& src)
|
||||
Mat Identify::image_2_hist(const cv::Mat& src)
|
||||
{
|
||||
Mat src_hsv;
|
||||
cvtColor(src, src_hsv, COLOR_BGR2HSV);
|
||||
@@ -49,7 +49,7 @@ Mat Identify::image2hist(const cv::Mat& src)
|
||||
double Identify::image_hist_comp(const cv::Mat& src, const cv::MatND& hist)
|
||||
{
|
||||
// keep the interface return value unchanged
|
||||
return 1 - compareHist(image2hist(src), hist, CV_COMP_BHATTACHARYYA);
|
||||
return 1 - compareHist(image_2_hist(src), hist, CV_COMP_BHATTACHARYYA);
|
||||
}
|
||||
|
||||
std::pair<double, cv::Point> Identify::find_image(const cv::Mat& image, const cv::Mat& templ)
|
||||
@@ -74,21 +74,22 @@ std::tuple<AlgorithmType, double, asst::Rect> Identify::find_image(const Mat& cu
|
||||
return { AlgorithmType::JustReturn, 0, asst::Rect() };
|
||||
}
|
||||
|
||||
// 有缓存,用直方图比较,CPU占用会低很多,但要保证每次按钮图片的位置不变
|
||||
if (m_use_cache && m_cache_map.find(templ) != m_cache_map.cend()) {
|
||||
auto&& [rect, hist] = m_cache_map.at(templ);
|
||||
double value = image_hist_comp(cur(rect), hist);
|
||||
return { AlgorithmType::CompareHist, value, cvrect2rect(rect).center_zoom(0.8) };
|
||||
return { AlgorithmType::CompareHist, value, cvrect_2_rect(rect).center_zoom(0.8) };
|
||||
}
|
||||
else {
|
||||
else { // 没缓存就模板匹配
|
||||
auto&& templ_mat = m_mat_map.at(templ);
|
||||
auto&& [value, point] = find_image(cur, templ_mat);
|
||||
cv::Rect raw_rect(point.x, point.y, templ_mat.cols, templ_mat.rows);
|
||||
|
||||
if (m_use_cache && value >= threshold) {
|
||||
m_cache_map.emplace(templ, std::make_pair(raw_rect, image2hist(cur(raw_rect))));
|
||||
m_cache_map.emplace(templ, std::make_pair(raw_rect, image_2_hist(cur(raw_rect))));
|
||||
}
|
||||
|
||||
return { AlgorithmType::MatchTemplate, value, cvrect2rect(raw_rect).center_zoom(0.8) };
|
||||
return { AlgorithmType::MatchTemplate, value, cvrect_2_rect(raw_rect).center_zoom(0.8) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
#include <stdint.h>
|
||||
#include <WinUser.h>
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#include "AsstDef.h"
|
||||
#include "Logger.hpp"
|
||||
#include "Configer.h"
|
||||
|
||||
using namespace asst;
|
||||
|
||||
@@ -40,8 +43,8 @@ bool WinMacro::findHandle()
|
||||
break;
|
||||
case HandleType::Control:
|
||||
m_is_adb = m_emulator_info.is_adb;
|
||||
m_x_offset = m_emulator_info.x_offset;
|
||||
m_y_offset = m_emulator_info.y_offset;
|
||||
//m_x_offset = m_emulator_info.x_offset;
|
||||
//m_y_offset = m_emulator_info.y_offset;
|
||||
handle_vec = m_emulator_info.control;
|
||||
break;
|
||||
default:
|
||||
@@ -96,11 +99,21 @@ bool WinMacro::findHandle()
|
||||
adb_dir = '"' + StringReplaceAll(m_emulator_info.adb.path, "[EmulatorPath]", adb_dir) + '"';
|
||||
std::string connect_cmd = adb_dir + m_emulator_info.adb.connect;
|
||||
|
||||
auto ret = callCmd(connect_cmd, 10000);
|
||||
if (ret != 0) {
|
||||
DebugTraceError("Connect ADB Error", ret);
|
||||
if (!callCmd(connect_cmd)) {
|
||||
DebugTraceError("Connect Adb Error");
|
||||
return false;
|
||||
}
|
||||
auto display_ret = callCmd(adb_dir + m_emulator_info.adb.display);
|
||||
if (display_ret) {
|
||||
std::string pipe_str = std::move(display_ret).value();
|
||||
sscanf_s(pipe_str.c_str(), m_emulator_info.adb.display_regex.c_str(),
|
||||
&m_emulator_info.adb.display_width, &m_emulator_info.adb.display_height);
|
||||
}
|
||||
else {
|
||||
DebugTraceError("Get Display Error");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_click_cmd = adb_dir + m_emulator_info.adb.click;
|
||||
}
|
||||
DebugTrace("Handle:", m_handle, "Name:", m_emulator_info.name, "Type:", m_handle_type);
|
||||
@@ -113,32 +126,69 @@ bool WinMacro::findHandle()
|
||||
}
|
||||
}
|
||||
|
||||
DWORD asst::WinMacro::callCmd(const std::string& cmd, int wait_time)
|
||||
std::optional<std::string> asst::WinMacro::callCmd(const std::string& cmd, bool use_pipe)
|
||||
{
|
||||
// int ret = system(cmd.c_str());
|
||||
// 初始化管道
|
||||
constexpr int PipeBuffSize = 1024;
|
||||
HANDLE pipe_read = NULL;
|
||||
HANDLE pipe_write = NULL;
|
||||
SECURITY_ATTRIBUTES sa_out_pipe;
|
||||
::ZeroMemory(&sa_out_pipe, sizeof(sa_out_pipe));
|
||||
sa_out_pipe.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa_out_pipe.lpSecurityDescriptor = NULL;
|
||||
sa_out_pipe.bInheritHandle = TRUE;
|
||||
BOOL pipe_ret = FALSE;
|
||||
if (use_pipe) {
|
||||
pipe_ret = ::CreatePipe(&pipe_read, &pipe_write, &sa_out_pipe, PipeBuffSize);
|
||||
DebugTrace("Create Pipe ret", pipe_ret);
|
||||
}
|
||||
|
||||
// 准备启动ADB进程
|
||||
STARTUPINFOA startup_info;
|
||||
PROCESS_INFORMATION process_info;
|
||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
||||
ZeroMemory(&process_info, sizeof(process_info));
|
||||
startup_info.cb = sizeof(STARTUPINFO);
|
||||
startup_info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||
startup_info.hStdOutput = pipe_write;
|
||||
startup_info.hStdError = pipe_write;
|
||||
startup_info.wShowWindow = SW_HIDE;
|
||||
|
||||
DWORD ret = -1;
|
||||
|
||||
if (::CreateProcessA(NULL, const_cast<LPSTR>(cmd.c_str()), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startup_info, &process_info)) {
|
||||
|
||||
::WaitForSingleObject(process_info.hProcess, wait_time);
|
||||
|
||||
::GetExitCodeProcess(process_info.hProcess, &ret);
|
||||
|
||||
::CloseHandle(process_info.hProcess);
|
||||
::CloseHandle(process_info.hThread);
|
||||
DebugTrace("Call", cmd, "—— ret", ret);
|
||||
}
|
||||
else {
|
||||
if (!::CreateProcessA(NULL, const_cast<LPSTR>(cmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startup_info, &process_info)) {
|
||||
DebugTraceError("Create process error");
|
||||
return std::nullopt;
|
||||
}
|
||||
::WaitForSingleObject(process_info.hProcess, 30000);
|
||||
|
||||
std::string pipe_str;
|
||||
if (use_pipe && pipe_ret) {
|
||||
DWORD read_num = 0;
|
||||
DWORD std_num = 0;
|
||||
if (::PeekNamedPipe(pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) {
|
||||
char pipe_buffer[PipeBuffSize] = { 0 };
|
||||
::ReadFile(pipe_read, pipe_buffer, read_num, &std_num, NULL);
|
||||
pipe_str = std::string(pipe_buffer, std_num);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
::GetExitCodeProcess(process_info.hProcess, &ret);
|
||||
|
||||
::CloseHandle(process_info.hProcess);
|
||||
::CloseHandle(process_info.hThread);
|
||||
DebugTrace("Call", cmd, "ret", ret);
|
||||
if (use_pipe) {
|
||||
DebugTrace("Pipe:", pipe_str);
|
||||
}
|
||||
|
||||
if (pipe_read != NULL) {
|
||||
::CloseHandle(pipe_read);
|
||||
}
|
||||
if (pipe_write != NULL) {
|
||||
::CloseHandle(pipe_write);
|
||||
}
|
||||
|
||||
return pipe_str;
|
||||
}
|
||||
|
||||
bool WinMacro::resizeWindow(int width, int height)
|
||||
@@ -216,25 +266,19 @@ bool WinMacro::click(const Point& p)
|
||||
return false;
|
||||
}
|
||||
|
||||
int x = p.x * m_control_scale;
|
||||
int y = p.y * m_control_scale;
|
||||
DebugTrace("Click, raw:", p.x, p.y, "corr:", x, y);
|
||||
|
||||
if (m_is_adb) {
|
||||
int x = (p.x + m_x_offset);
|
||||
int y = (p.y + m_y_offset);
|
||||
std::string cur_cmd = StringReplaceAll(m_click_cmd, "[x]", std::to_string(x));
|
||||
cur_cmd = StringReplaceAll(cur_cmd, "[y]", std::to_string(y));
|
||||
auto ret = callCmd(cur_cmd);
|
||||
DebugTrace("Call", cur_cmd, "—— ret", ret);
|
||||
return !ret;
|
||||
return callCmd(cur_cmd, false).has_value();
|
||||
}
|
||||
else {
|
||||
int x = (p.x + m_x_offset) / getScreenScale();
|
||||
int y = (p.y + m_y_offset) / getScreenScale();
|
||||
DebugTrace("Click, raw:", p.x, p.y, "corr:", x, y);
|
||||
|
||||
LPARAM lparam = MAKELPARAM(x, y);
|
||||
|
||||
::SendMessage(m_handle, WM_LBUTTONDOWN, MK_LBUTTON, lparam);
|
||||
::SendMessage(m_handle, WM_LBUTTONUP, 0, lparam);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -266,6 +310,16 @@ bool WinMacro::click(const Rect& rect)
|
||||
return click({ x, y });
|
||||
}
|
||||
|
||||
void asst::WinMacro::setControlScale(double scale)
|
||||
{
|
||||
if (m_is_adb) {
|
||||
m_control_scale = scale * scale * Configer::DefaultWindowWidth / m_emulator_info.adb.display_width;
|
||||
}
|
||||
else {
|
||||
m_control_scale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
Rect WinMacro::getWindowRect()
|
||||
{
|
||||
if (!::IsWindow(m_handle)) {
|
||||
|
||||
11
README.md
11
README.md
@@ -45,14 +45,14 @@ A game assistance for Arknights
|
||||
|
||||
#### 腾讯手游助手
|
||||
|
||||
兼容,但需要手动设置分辨率:设置中心——引擎设置——分辨率设置——1280x720——保存后重启模拟器
|
||||
理论上完美兼容,未多做测试
|
||||
|
||||
#### MuMu模拟器
|
||||
|
||||
MuMu是个奇葩,它的所有的窗口句柄均不响应SendMessage鼠标消息
|
||||
|
||||
- MuMu模拟器
|
||||
兼容(使用了adb控制的方式)。但需要手动设置分辨率:设置中心——界面——分辨率设置——1280x720——保存后重启模拟器
|
||||
完美兼容,专门单独做了一套基于Adb的控制逻辑
|
||||
- MuMu手游助手(星云引擎)
|
||||
不兼容,正在想办法……
|
||||
|
||||
@@ -76,6 +76,7 @@ MuMu是个奇葩,它的所有的窗口句柄均不响应SendMessage鼠标消
|
||||
1. 明日方舟处于任意界面均可,会自动帮你点过去
|
||||
2. 点击"访问基建"
|
||||
3. 达到10次上限,或者所有可访问的好友都访问完了,就会自动停的
|
||||
4. 然后会贴心的帮你跳转到信用商店,顺便收了当天信用~
|
||||
|
||||
### 设置操作延时
|
||||
|
||||
@@ -83,6 +84,12 @@ MuMu是个奇葩,它的所有的窗口句柄均不响应SendMessage鼠标消
|
||||
|
||||

|
||||
|
||||
### 自动截图功能
|
||||
|
||||
每次刷完结算界面,会自动截一张图,保存在`screenshot`文件夹中
|
||||
这个功能默认是打开的,不需要的话可以手动关掉:请手动修改`resource\config.json`文件中,`options`.`printWindow`字段的值,`true`是打开,`false`是关闭。文件保存后请重新打开程序。
|
||||

|
||||
|
||||
## Todo
|
||||
|
||||
~~在做了在做了.jpg~~
|
||||
|
||||
BIN
readme/printWindow.png
Normal file
BIN
readme/printWindow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -2,14 +2,20 @@
|
||||
"version": "0.4",
|
||||
"options": {
|
||||
"identifyCache": true,
|
||||
"identifyCache_Doc": "图像识别缓存功能:开启后可以大幅降低CPU消耗,但需要保证要识别的按钮每次的位置不会改变。true-开启,false-关闭,默认true",
|
||||
"identifyDelay": 1000,
|
||||
"identifyDelay_Doc": "图像识别延时:越快操作越快,但会增加CPU消耗。单位毫秒,默认1000",
|
||||
"printWindow": true,
|
||||
"printWindow_Doc": "截图功能:开启后每次结算界面会截图到screenshot目录下。true-开启,false-关闭,默认true",
|
||||
"printWindowDelay": 3000,
|
||||
"printWindowDelay_Doc": "截图延时:每次到结算界面,掉落物品不是一次性出来的,有个动画,所以需要等一会再截图。单位毫秒,默认3000",
|
||||
"controlDelayRange": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
],
|
||||
"controlDelayRange_Doc": "点击随机延时:每次点击操作会进行随机延时,降低封号风险(好像也没听说过谁被封号的)。格式为 [ 最小延时, 最大延时 ],单位为毫秒。例如想设置3~5秒延时,即修改为[ 3000, 5000 ],默认0~0"
|
||||
},
|
||||
"handle_Doc": "下面的和模拟器窗口捕获逻辑有关,不需要修改",
|
||||
"handle": {
|
||||
"BlueStacks": {
|
||||
"window": [
|
||||
@@ -40,8 +46,10 @@
|
||||
],
|
||||
"width": 1294,
|
||||
"height": 773,
|
||||
"xOffset": -7,
|
||||
"yOffset": -47
|
||||
"xOffset": 11,
|
||||
"yOffset": 46,
|
||||
"rightOffset": 11,
|
||||
"bottomOffset": 10
|
||||
},
|
||||
"Nox": {
|
||||
"window": [
|
||||
@@ -65,7 +73,7 @@
|
||||
"width": 1298,
|
||||
"height": 754,
|
||||
"xOffset": 0,
|
||||
"yOffset": -32
|
||||
"yOffset": 32
|
||||
},
|
||||
"LDPlayer": {
|
||||
"window": [
|
||||
@@ -93,7 +101,7 @@
|
||||
"width": 1304,
|
||||
"height": 756,
|
||||
"xOffset": 0,
|
||||
"yOffset": -35
|
||||
"yOffset": 35
|
||||
},
|
||||
"XYAZ": {
|
||||
"window": [
|
||||
@@ -121,7 +129,7 @@
|
||||
"width": 1282,
|
||||
"height": 769,
|
||||
"xOffset": 0,
|
||||
"yOffset": -47
|
||||
"yOffset": 47
|
||||
},
|
||||
"Tencent": {
|
||||
"window": [
|
||||
@@ -149,7 +157,7 @@
|
||||
"width": 1282,
|
||||
"height": 769,
|
||||
"xOffset": 0,
|
||||
"yOffset": -42
|
||||
"yOffset": 42
|
||||
},
|
||||
"MuMuAsst": {
|
||||
"window": [
|
||||
@@ -178,7 +186,7 @@
|
||||
"width": 1280,
|
||||
"height": 809,
|
||||
"xOffset": 0,
|
||||
"yOffset": -36
|
||||
"yOffset": 36
|
||||
},
|
||||
"MuMuEmulator": {
|
||||
"window": [
|
||||
@@ -199,17 +207,22 @@
|
||||
"window": "明日方舟 - MuMu模拟器"
|
||||
}
|
||||
],
|
||||
"adbControl": {
|
||||
"adb": {
|
||||
"path": "[EmulatorPath]..\\vmonitor\\bin\\adb_server.exe",
|
||||
"connect": " connect 127.0.0.1:7555",
|
||||
"click": " shell input tap [x] [y]"
|
||||
"click": " shell input tap [x] [y]",
|
||||
"display": " shell dumpsys window displays | grep init= | awk ' { print $3 } '",
|
||||
"displayRegex": "cur=%dx%d"
|
||||
},
|
||||
"width": 1280,
|
||||
"height": 809,
|
||||
"xOffset": 0,
|
||||
"yOffset": -36
|
||||
"yOffset": 36,
|
||||
"rightBottom": 0,
|
||||
"bottomOffset": 53
|
||||
}
|
||||
},
|
||||
"tasks_Doc": "下面的和任务队列执行逻辑有关,不需要修改",
|
||||
"tasks": {
|
||||
"SanityBegin": {
|
||||
"filename": "",
|
||||
@@ -363,6 +376,8 @@
|
||||
},
|
||||
"ReturnToFriends": {
|
||||
"filename": "Return.png",
|
||||
"threshold": 0.85,
|
||||
"cacheThreshold": 0.85,
|
||||
"type": "clickSelf",
|
||||
"next": [
|
||||
"Friends",
|
||||
|
||||
Reference in New Issue
Block a user