mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-16 17:57:01 +08:00
feat: 初步实现Mac平台的原生Controller
This commit is contained in:
@@ -86,6 +86,10 @@ bool asst::Assistant::set_instance_option(InstanceOptionKey key, const std::stri
|
||||
m_ctrler->set_touch_mode(TouchMode::Maatouch);
|
||||
return true;
|
||||
}
|
||||
else if (constexpr std::string_view MacPlayTools = "MacPlayTools"; value == MacPlayTools) {
|
||||
m_ctrler->set_touch_mode(TouchMode::MacPlayTools);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case InstanceOptionKey::DeploymentWithPause:
|
||||
if (constexpr std::string_view Enable = "1"; value == Enable) {
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace asst
|
||||
Adb = 0,
|
||||
Minitouch = 1,
|
||||
Maatouch = 2,
|
||||
MacPlayTools = 3,
|
||||
};
|
||||
|
||||
namespace ControlFeat
|
||||
|
||||
@@ -229,6 +229,9 @@ void asst::Controller::set_touch_mode(const TouchMode& mode) noexcept
|
||||
case TouchMode::Maatouch:
|
||||
m_controller_type = ControllerType::Maatouch;
|
||||
break;
|
||||
case TouchMode::MacPlayTools:
|
||||
m_controller_type = ControllerType::MacPlayTools;
|
||||
break;
|
||||
default:
|
||||
m_controller_type = ControllerType::Minitouch;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace asst
|
||||
Adb,
|
||||
Minitouch,
|
||||
Maatouch,
|
||||
MacPlayTools,
|
||||
};
|
||||
|
||||
class ControllerAPI
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "AdbController.h"
|
||||
#include "ControllerAPI.h"
|
||||
#include "MaatouchController.h"
|
||||
#include "PlayToolsController.h"
|
||||
#include "MinitouchController.h"
|
||||
|
||||
namespace asst
|
||||
@@ -29,6 +30,9 @@ namespace asst
|
||||
case ControllerType::Maatouch:
|
||||
controller = std::make_shared<MaatouchController>(m_callback, m_inst, platform_type);
|
||||
break;
|
||||
case ControllerType::MacPlayTools:
|
||||
controller = std::make_shared<PlayToolsController>(m_callback, m_inst, platform_type);
|
||||
break;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
252
src/MaaCore/Controller/PlayToolsController.cpp
Normal file
252
src/MaaCore/Controller/PlayToolsController.cpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#include "PlayToolsController.h"
|
||||
|
||||
#include <asio.hpp>
|
||||
|
||||
#include "Config/GeneralConfig.h"
|
||||
#include "Utils/NoWarningCV.h"
|
||||
|
||||
using asio::ip::tcp;
|
||||
namespace socket_ops = asio::detail::socket_ops;
|
||||
|
||||
asst::PlayToolsController::PlayToolsController(const AsstCallback& callback, Assistant* inst,
|
||||
PlatformType type [[maybe_unused]])
|
||||
: InstHelper(inst), m_callback(callback), m_socket(m_context)
|
||||
{
|
||||
LogTraceFunction;
|
||||
}
|
||||
|
||||
asst::PlayToolsController::~PlayToolsController()
|
||||
{
|
||||
std::error_code ec;
|
||||
m_socket.shutdown(tcp::socket::shutdown_both, ec);
|
||||
m_socket.close(ec);
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::connect(const std::string& adb_path [[maybe_unused]], const std::string& address,
|
||||
const std::string& config [[maybe_unused]])
|
||||
{
|
||||
if (m_socket.is_open()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string host, port;
|
||||
std::stringstream ss(address);
|
||||
std::getline(ss, host, ':');
|
||||
std::getline(ss, port);
|
||||
|
||||
tcp::resolver resolver(m_context);
|
||||
|
||||
try {
|
||||
std::array<uint8_t, 4> buffer;
|
||||
constexpr char handshake[4] = { 'M', 'A', 'A', 0 };
|
||||
constexpr char signature[4] = { 'O', 'K', 'A', 'Y' };
|
||||
asio::connect(m_socket, resolver.resolve(host, port));
|
||||
asio::write(m_socket, asio::buffer(handshake));
|
||||
asio::read(m_socket, asio::buffer(buffer, 4));
|
||||
|
||||
if (memcmp(&buffer, signature, 4)) {
|
||||
Log.error("Got invalid response:", buffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log.error("Cannot connect to", address, e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
uint16_t width = 0, height = 0;
|
||||
constexpr char request[6] = { 0, 4, 'S', 'I', 'Z', 'E' };
|
||||
asio::write(m_socket, asio::buffer(request));
|
||||
asio::read(m_socket, asio::buffer(&width, sizeof(width)));
|
||||
asio::read(m_socket, asio::buffer(&height, sizeof(height)));
|
||||
|
||||
width = socket_ops::network_to_host_short(width);
|
||||
height = socket_ops::network_to_host_short(height);
|
||||
|
||||
m_screen_size = { width, height };
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log.error("Cannot get screen resolution:", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::inited() const noexcept
|
||||
{
|
||||
return m_socket.is_open() && m_screen_size.first > 0;
|
||||
}
|
||||
|
||||
const std::string& asst::PlayToolsController::get_uuid() const
|
||||
{
|
||||
const static std::string uuid("com.hypergryph.arknights");
|
||||
return uuid;
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::screencap(cv::Mat& image_payload, bool allow_reconnect [[maybe_unused]])
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
uint32_t image_size = 0;
|
||||
|
||||
try {
|
||||
constexpr char request[6] = { 0, 4, 'S', 'C', 'R', 'N' };
|
||||
asio::write(m_socket, asio::buffer(request));
|
||||
asio::read(m_socket, asio::buffer(&image_size, sizeof(image_size)));
|
||||
image_size = socket_ops::network_to_host_long(image_size);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log.error("Cannot get screencap:", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (image_size == 0) {
|
||||
Log.error("Cannot get screencap: invalid image size");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
std::vector<uint8_t> buffer(image_size);
|
||||
asio::read(m_socket, asio::buffer(buffer, image_size));
|
||||
image_payload = cv::Mat(m_screen_size.second, m_screen_size.first, CV_8UC4, buffer.data());
|
||||
cv::cvtColor(image_payload, image_payload, cv::COLOR_RGB2BGR);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log.error("Cannot get screencap:", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::start_game(const std::string& client_type [[maybe_unused]])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::stop_game()
|
||||
{
|
||||
try {
|
||||
constexpr char request[6] = { 0, 4, 'T', 'E', 'R', 'M' };
|
||||
asio::write(m_socket, asio::buffer(request));
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log.error("Cannot terminate game:", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::click(const Point& p)
|
||||
{
|
||||
return toucher_down(p) && toucher_up(p);
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::swipe(const Point& p1, const Point& p2, int duration, bool extra_swipe, double slope_in,
|
||||
double slope_out, bool with_pause [[maybe_unused]])
|
||||
{
|
||||
int x1 = p1.x, y1 = p1.y;
|
||||
int x2 = p2.x, y2 = p2.y;
|
||||
|
||||
const auto width = m_screen_size.first;
|
||||
const auto height = m_screen_size.second;
|
||||
|
||||
// 起点不能在屏幕外,但是终点可以
|
||||
if (x1 < 0 || x1 >= width || y1 < 0 || y1 >= height) {
|
||||
Log.warn("swipe point1 is out of range", x1, y1);
|
||||
x1 = std::clamp(x1, 0, width - 1);
|
||||
y1 = std::clamp(y1, 0, height - 1);
|
||||
}
|
||||
|
||||
toucher_down(p1);
|
||||
|
||||
auto cubic_spline = [](double slope_0, double slope_1, double t) {
|
||||
const double a = slope_0;
|
||||
const double b = -(2 * slope_0 + slope_1 - 3);
|
||||
const double c = -(-slope_0 - slope_1 + 2);
|
||||
return a * t + b * std::pow(t, 2) + c * std::pow(t, 3);
|
||||
}; // TODO: move this to math.hpp
|
||||
|
||||
const auto progressive_move = [&](int _x1, int _y1, int _x2, int _y2, int _duration) {
|
||||
for (int cur_time = DefaultSwipeDelay; cur_time < _duration; cur_time += DefaultSwipeDelay) {
|
||||
double progress = cubic_spline(slope_in, slope_out, static_cast<double>(cur_time) / duration);
|
||||
int cur_x = static_cast<int>(std::lerp(_x1, _x2, progress));
|
||||
int cur_y = static_cast<int>(std::lerp(_y1, _y2, progress));
|
||||
if (cur_x < 0 || cur_x > width || cur_y < 0 || cur_y > height) {
|
||||
continue;
|
||||
}
|
||||
toucher_move({ cur_x, cur_y });
|
||||
}
|
||||
if (_x2 >= 0 && _x2 <= width && _y2 >= 0 && _y2 <= height) {
|
||||
toucher_move({ _x2, _y2 });
|
||||
}
|
||||
};
|
||||
|
||||
constexpr int DefaultDuration = 200;
|
||||
progressive_move(x1, y1, x2, y2, duration ? duration : DefaultDuration);
|
||||
|
||||
const auto& opt = Config.get_options();
|
||||
if (extra_swipe && opt.minitouch_extra_swipe_duration > 0) {
|
||||
constexpr int ExtraEndDelay = 100; // 停留终点
|
||||
toucher_wait(ExtraEndDelay);
|
||||
progressive_move(x2, y2, x2, y2 - opt.minitouch_extra_swipe_dist, opt.minitouch_extra_swipe_duration);
|
||||
}
|
||||
|
||||
return toucher_up(p2);
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::press_esc()
|
||||
{
|
||||
Log.info("ESC is not supported on iOS");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<int, int> asst::PlayToolsController::get_screen_res() const noexcept
|
||||
{
|
||||
return m_screen_size;
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::toucher_down(const Point& p, const int delay)
|
||||
{
|
||||
return toucher_commit(TouchPhase::Began, p, delay);
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::toucher_move(const Point& p, const int delay)
|
||||
{
|
||||
return toucher_commit(TouchPhase::Moved, p, delay);
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::toucher_up(const Point& p, const int delay)
|
||||
{
|
||||
return toucher_commit(TouchPhase::Ended, p, delay);
|
||||
}
|
||||
|
||||
void asst::PlayToolsController::toucher_wait(const int delay)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
|
||||
}
|
||||
|
||||
bool asst::PlayToolsController::toucher_commit(const TouchPhase phase, const Point& p, const int delay)
|
||||
{
|
||||
uint16_t x = socket_ops::host_to_network_short(static_cast<uint16_t>(p.x));
|
||||
uint16_t y = socket_ops::host_to_network_short(static_cast<uint16_t>(p.y));
|
||||
uint8_t payload[5] = { static_cast<uint8_t>(phase), 0, 0, 0, 0 };
|
||||
std::memcpy(payload + 1, &x, sizeof(x));
|
||||
std::memcpy(payload + 3, &y, sizeof(y));
|
||||
|
||||
try {
|
||||
constexpr char request[6] = { 0, 9, 'T', 'U', 'C', 'H' };
|
||||
asio::write(m_socket, asio::buffer(request));
|
||||
asio::write(m_socket, asio::buffer(payload, 5));
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log.error("Cannot touch screen:", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
toucher_wait(delay);
|
||||
return true;
|
||||
}
|
||||
79
src/MaaCore/Controller/PlayToolsController.h
Normal file
79
src/MaaCore/Controller/PlayToolsController.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "ControllerAPI.h"
|
||||
#include "MinitouchController.h"
|
||||
|
||||
#include <asio/io_context.hpp>
|
||||
#include <asio/ip/tcp.hpp>
|
||||
|
||||
#include "Platform/PlatformFactory.h"
|
||||
|
||||
#include "Common/AsstMsg.h"
|
||||
#include "InstHelper.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class PlayToolsController : public ControllerAPI, protected InstHelper
|
||||
{
|
||||
public:
|
||||
PlayToolsController(const AsstCallback& callback, Assistant* inst, PlatformType type);
|
||||
PlayToolsController(const PlayToolsController&) = delete;
|
||||
PlayToolsController(PlayToolsController&&) = delete;
|
||||
virtual ~PlayToolsController();
|
||||
|
||||
virtual bool connect(const std::string& adb_path, const std::string& address,
|
||||
const std::string& config) override;
|
||||
virtual bool inited() const noexcept override;
|
||||
|
||||
virtual void set_swipe_with_pause([[maybe_unused]] bool enable) noexcept override {}
|
||||
|
||||
virtual const std::string& get_uuid() const override;
|
||||
|
||||
virtual bool screencap(cv::Mat& image_payload, bool allow_reconnect = false) override;
|
||||
|
||||
virtual bool start_game(const std::string& client_type) override;
|
||||
virtual bool stop_game() override;
|
||||
|
||||
virtual bool click(const Point& p) override;
|
||||
|
||||
virtual bool swipe(const Point& p1, const Point& p2, int duration = 0, bool extra_swipe = false,
|
||||
double slope_in = 1, double slope_out = 1, bool with_pause = false) override;
|
||||
|
||||
virtual bool inject_input_event([[maybe_unused]] const InputEvent& event) override { return false; }
|
||||
|
||||
virtual bool press_esc() override;
|
||||
virtual ControlFeat::Feat support_features() const noexcept override { return ControlFeat::NONE; }
|
||||
|
||||
virtual std::pair<int, int> get_screen_res() const noexcept override;
|
||||
|
||||
PlayToolsController& operator=(const PlayToolsController&) = delete;
|
||||
PlayToolsController& operator=(PlayToolsController&&) = delete;
|
||||
|
||||
protected:
|
||||
AsstCallback m_callback;
|
||||
|
||||
asio::io_context m_context;
|
||||
asio::ip::tcp::socket m_socket;
|
||||
|
||||
std::pair<int, int> m_screen_size = { 0, 0 };
|
||||
|
||||
enum class TouchPhase
|
||||
{
|
||||
Began = 0,
|
||||
Moved = 1,
|
||||
Ended = 3,
|
||||
};
|
||||
|
||||
static constexpr int DefaultClickDelay = 50;
|
||||
static constexpr int DefaultSwipeDelay = 5;
|
||||
|
||||
bool toucher_down(const Point& p, const int delay = DefaultClickDelay);
|
||||
bool toucher_move(const Point& p, const int delay = DefaultSwipeDelay);
|
||||
bool toucher_up(const Point& p, const int delay = DefaultClickDelay);
|
||||
|
||||
void toucher_wait(const int delay);
|
||||
|
||||
private:
|
||||
bool toucher_commit(const TouchPhase phase, const Point& p, const int delay);
|
||||
};
|
||||
} // namespace asst
|
||||
@@ -48,6 +48,7 @@
|
||||
<ClInclude Include="Controller\ControlScaleProxy.h" />
|
||||
<ClInclude Include="Controller\MaatouchController.h" />
|
||||
<ClInclude Include="Controller\MinitouchController.h" />
|
||||
<ClInclude Include="Controller\PlayToolsController.h" />
|
||||
<ClInclude Include="Controller\AdbController.h" />
|
||||
<ClInclude Include="Controller\Platform\AdbLiteIO.h" />
|
||||
<ClInclude Include="Controller\Platform\PosixIO.h" />
|
||||
@@ -192,6 +193,7 @@
|
||||
<ClCompile Include="Controller\Controller.cpp" />
|
||||
<ClCompile Include="Controller\ControlScaleProxy.cpp" />
|
||||
<ClCompile Include="Controller\MinitouchController.cpp" />
|
||||
<ClCompile Include="Controller\PlayToolsController.cpp" />
|
||||
<ClCompile Include="Controller\AdbController.cpp" />
|
||||
<ClCompile Include="Controller\Platform\AdbLiteIO.cpp" />
|
||||
<ClCompile Include="Controller\Platform\PosixIO.cpp" />
|
||||
|
||||
@@ -83,7 +83,7 @@ bool asst::InfrastDormTask::opers_choose(asst::infrast::CustomRoomConfig const&
|
||||
size_t num_of_selected = m_is_custom ? current_room_config().selected : 0;
|
||||
size_t num_of_fulltrust = 0;
|
||||
bool to_fill = false;
|
||||
int swipe_times = 0;
|
||||
int swipe_times [[maybe_unused]] = 0;
|
||||
|
||||
while (num_of_selected < max_num_of_opers()) {
|
||||
if (need_exit()) {
|
||||
|
||||
Reference in New Issue
Block a user