diff --git a/src/MaaCore/Assistant.cpp b/src/MaaCore/Assistant.cpp index bb92b2ebf6..78e9e58f12 100644 --- a/src/MaaCore/Assistant.cpp +++ b/src/MaaCore/Assistant.cpp @@ -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) { diff --git a/src/MaaCore/Common/AsstTypes.h b/src/MaaCore/Common/AsstTypes.h index ce1841bc9a..9754f5c482 100644 --- a/src/MaaCore/Common/AsstTypes.h +++ b/src/MaaCore/Common/AsstTypes.h @@ -48,6 +48,7 @@ namespace asst Adb = 0, Minitouch = 1, Maatouch = 2, + MacPlayTools = 3, }; namespace ControlFeat diff --git a/src/MaaCore/Controller/Controller.cpp b/src/MaaCore/Controller/Controller.cpp index 1ab35323d7..60329cb728 100644 --- a/src/MaaCore/Controller/Controller.cpp +++ b/src/MaaCore/Controller/Controller.cpp @@ -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; } diff --git a/src/MaaCore/Controller/ControllerAPI.h b/src/MaaCore/Controller/ControllerAPI.h index 795b01a969..5af0152d4e 100644 --- a/src/MaaCore/Controller/ControllerAPI.h +++ b/src/MaaCore/Controller/ControllerAPI.h @@ -14,6 +14,7 @@ namespace asst Adb, Minitouch, Maatouch, + MacPlayTools, }; class ControllerAPI diff --git a/src/MaaCore/Controller/ControllerFactory.h b/src/MaaCore/Controller/ControllerFactory.h index dc33dc7791..fa0af26522 100644 --- a/src/MaaCore/Controller/ControllerFactory.h +++ b/src/MaaCore/Controller/ControllerFactory.h @@ -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(m_callback, m_inst, platform_type); break; + case ControllerType::MacPlayTools: + controller = std::make_shared(m_callback, m_inst, platform_type); + break; default: return nullptr; } diff --git a/src/MaaCore/Controller/PlayToolsController.cpp b/src/MaaCore/Controller/PlayToolsController.cpp new file mode 100644 index 0000000000..ea009e6abc --- /dev/null +++ b/src/MaaCore/Controller/PlayToolsController.cpp @@ -0,0 +1,252 @@ +#include "PlayToolsController.h" + +#include + +#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 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 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(cur_time) / duration); + int cur_x = static_cast(std::lerp(_x1, _x2, progress)); + int cur_y = static_cast(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 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(p.x)); + uint16_t y = socket_ops::host_to_network_short(static_cast(p.y)); + uint8_t payload[5] = { static_cast(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; +} diff --git a/src/MaaCore/Controller/PlayToolsController.h b/src/MaaCore/Controller/PlayToolsController.h new file mode 100644 index 0000000000..a6b5b18970 --- /dev/null +++ b/src/MaaCore/Controller/PlayToolsController.h @@ -0,0 +1,79 @@ +#pragma once + +#include "ControllerAPI.h" +#include "MinitouchController.h" + +#include +#include + +#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 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 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 diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj index 64f25745f3..2742a93c36 100644 --- a/src/MaaCore/MaaCore.vcxproj +++ b/src/MaaCore/MaaCore.vcxproj @@ -48,6 +48,7 @@ + @@ -192,6 +193,7 @@ + diff --git a/src/MaaCore/Task/Infrast/InfrastDormTask.cpp b/src/MaaCore/Task/Infrast/InfrastDormTask.cpp index bc73f72e5a..1eb48abcc7 100644 --- a/src/MaaCore/Task/Infrast/InfrastDormTask.cpp +++ b/src/MaaCore/Task/Infrast/InfrastDormTask.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()) {