diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..0bf4e53aac --- /dev/null +++ b/.editorconfig @@ -0,0 +1,81 @@ +# Visual Studio generated .editorconfig file with C++ settings. +root = true + +[*] +indent_style = space +insert_final_newline = true +charset = utf-8 +end_of_line = lf + +[*.json] + +indent_size = 4 + +[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] + +indent_size = 4 + +# Visual C++ Code Style settings + +cpp_generate_documentation_comments = xml + +# Visual C++ Formatting settings + +cpp_indent_braces = false +cpp_indent_multi_line_relative_to = innermost_parenthesis +cpp_indent_within_parentheses = indent +cpp_indent_preserve_within_parentheses = true +cpp_indent_case_contents = true +cpp_indent_case_labels = false +cpp_indent_case_contents_when_block = false +cpp_indent_lambda_braces_when_parameter = true +cpp_indent_goto_labels = one_left +cpp_indent_preprocessor = leftmost_column +cpp_indent_access_specifiers = false +cpp_indent_namespace_contents = true +cpp_indent_preserve_comments = false +cpp_new_line_before_open_brace_namespace = new_line +cpp_new_line_before_open_brace_type = new_line +cpp_new_line_before_open_brace_function = new_line +cpp_new_line_before_open_brace_block = same_line +cpp_new_line_before_open_brace_lambda = same_line +cpp_new_line_scope_braces_on_separate_lines = false +cpp_new_line_close_brace_same_line_empty_type = false +cpp_new_line_close_brace_same_line_empty_function = false +cpp_new_line_before_catch = true +cpp_new_line_before_else = true +cpp_new_line_before_while_in_do_while = false +cpp_space_before_function_open_parenthesis = remove +cpp_space_within_parameter_list_parentheses = false +cpp_space_between_empty_parameter_list_parentheses = false +cpp_space_after_keywords_in_control_flow_statements = true +cpp_space_within_control_flow_statement_parentheses = false +cpp_space_before_lambda_open_parenthesis = false +cpp_space_within_cast_parentheses = false +cpp_space_after_cast_close_parenthesis = false +cpp_space_within_expression_parentheses = false +cpp_space_before_block_open_brace = true +cpp_space_between_empty_braces = false +cpp_space_before_initializer_list_open_brace = false +cpp_space_within_initializer_list_braces = true +cpp_space_preserve_in_initializer_list = true +cpp_space_before_open_square_bracket = false +cpp_space_within_square_brackets = false +cpp_space_before_empty_square_brackets = false +cpp_space_between_empty_square_brackets = false +cpp_space_group_square_brackets = true +cpp_space_within_lambda_brackets = false +cpp_space_between_empty_lambda_brackets = false +cpp_space_before_comma = false +cpp_space_after_comma = true +cpp_space_remove_around_member_operators = true +cpp_space_before_inheritance_colon = true +cpp_space_before_constructor_colon = true +cpp_space_remove_before_semicolon = true +cpp_space_after_semicolon = false +cpp_space_remove_around_unary_operator = true +cpp_space_around_binary_operator = insert +cpp_space_around_assignment_operator = insert +cpp_space_pointer_reference_alignment = left +cpp_space_around_ternary_operator = insert +cpp_wrap_preserve_blocks = one_liners diff --git a/include/AsstCaller.h b/include/AsstCaller.h index f517690a5d..d643e569bd 100644 --- a/include/AsstCaller.h +++ b/include/AsstCaller.h @@ -2,14 +2,15 @@ #include "AsstPort.h" -namespace asst { +namespace asst +{ class Assistant; } #ifdef __cplusplus extern "C" { #endif - typedef void (ASST_CALL* AsstCallback)(int msg, const char* detail_json, void* custom_arg); + typedef void(ASST_CALL* AsstCallback)(int msg, const char* detail_json, void* custom_arg); ASSTAPI_PORT asst::Assistant* ASST_CALL AsstCreate(const char* dirname); ASSTAPI_PORT asst::Assistant* ASST_CALL AsstCreateEx(const char* dirname, AsstCallback callback, void* custom_arg); @@ -25,7 +26,7 @@ extern "C" { bool ASSTAPI AsstAppendAward(asst::Assistant* p_asst); bool ASSTAPI AsstAppendVisit(asst::Assistant* p_asst); bool ASSTAPI AsstAppendMall(asst::Assistant* p_asst, bool with_shopping); - //bool ASSTAPI AsstAppendProcessTask(asst::Assistant* p_asst, const char* task_name); + // bool ASSTAPI AsstAppendProcessTask(asst::Assistant* p_asst, const char* task_name); bool ASSTAPI AsstAppendInfrast(asst::Assistant* p_asst, int work_mode, const char** order, int order_size, const char* uses_of_drones, double dorm_threshold); bool ASSTAPI AsstAppendRecruit(asst::Assistant* p_asst, int max_times, const int select_level[], int select_len, const int confirm_level[], int confirm_len, bool need_refresh); bool ASSTAPI AsstAppendDebug(asst::Assistant* p_asst); @@ -35,9 +36,9 @@ extern "C" { bool ASSTAPI AsstStop(asst::Assistant* p_asst); bool ASSTAPI AsstSetPenguinId(asst::Assistant* p_asst, const char* id); - //bool ASSTAPI AsstSetParam(asst::Assistant* p_asst, const char* type, const char* param, const char* value); + // bool ASSTAPI AsstSetParam(asst::Assistant* p_asst, const char* type, const char* param, const char* value); ASSTAPI_PORT const char* ASST_CALL AsstGetVersion(); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/include/AsstPort.h b/include/AsstPort.h index f426d7fef0..50c24437b9 100644 --- a/include/AsstPort.h +++ b/include/AsstPort.h @@ -16,9 +16,9 @@ #define ASST_DLL_LOCAL #else #if __GNUC__ >= 4 -#define ASST_DLL_IMPORT __attribute__ ((visibility ("default"))) -#define ASST_DLL_EXPORT __attribute__ ((visibility ("default"))) -#define ASST_DLL_LOCAL __attribute__ ((visibility ("hidden"))) +#define ASST_DLL_IMPORT __attribute__((visibility("default"))) +#define ASST_DLL_EXPORT __attribute__((visibility("default"))) +#define ASST_DLL_LOCAL __attribute__((visibility("hidden"))) #else #define ASST_DLL_IMPORT #define ASST_DLL_EXPORT @@ -34,4 +34,4 @@ #define ASSTAPI ASSTAPI_PORT ASST_CALL -#define ASSTLOCAL ASST_DLL_LOCAL ASST_CALL \ No newline at end of file +#define ASSTLOCAL ASST_DLL_LOCAL ASST_CALL diff --git a/resource/config.json b/resource/config.json index 75870982be..64517a752d 100644 --- a/resource/config.json +++ b/resource/config.json @@ -30,7 +30,9 @@ }, "adb": { "path": "[EmulatorPath]Engine\\ProgramFiles\\HD-Adb.exe", - "addresses": [ "127.0.0.1:5555" ], + "addresses": [ + "127.0.0.1:5555" + ], "devices": "[Adb] devices", "addressRegex": "(.+)\tdevice", "connect": "[Adb] connect [Address]", @@ -49,7 +51,9 @@ }, "adb": { "path": "[EmulatorPath]HD-Adb.exe", - "addresses": [ "127.0.0.1:5555" ], + "addresses": [ + "127.0.0.1:5555" + ], "devices": "[Adb] devices", "addressRegex": "(.+)\tdevice", "connect": "[Adb] connect [Address]", @@ -68,7 +72,9 @@ }, "adb": { "path": "[EmulatorPath]..\\vmonitor\\bin\\adb_server.exe", - "addresses": [ "127.0.0.1:7555" ], + "addresses": [ + "127.0.0.1:7555" + ], "devices": "[Adb] devices", "addressRegex": "(.+)\tdevice", "connect": "[Adb] connect [Address]", @@ -87,7 +93,10 @@ }, "adb": { "path": "[EmulatorPath]adb.exe", - "addresses": [ "127.0.0.1:5555", "emulator-5554" ], + "addresses": [ + "127.0.0.1:5555", + "emulator-5554" + ], "devices": "[Adb] devices", "addressRegex": "(.+)\tdevice", "connect": "[Adb] connect [Address]", @@ -106,7 +115,10 @@ }, "adb": { "path": "[EmulatorPath]nox_adb.exe", - "addresses": [ "127.0.0.1:62001", "127.0.0.1:59865" ], + "addresses": [ + "127.0.0.1:62001", + "127.0.0.1:59865" + ], "devices": "[Adb] devices", "addressRegex": "(.+)\tdevice", "connect": "[Adb] connect [Address]", @@ -125,7 +137,9 @@ }, "adb": { "path": "[EmulatorPath]adb.exe", - "addresses": [ "127.0.0.1:21503" ], + "addresses": [ + "127.0.0.1:21503" + ], "devices": "[Adb] devices", "addressRegex": "(.+)\tdevice", "connect": "[Adb] connect [Address]", diff --git a/resource/tasks.json b/resource/tasks.json index bfaf6ac7fb..daa1fdc33e 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -457,7 +457,6 @@ 500, 380, 150 - ], "action": "doNothing", "rearDelay": 5000, diff --git a/src/MeoAssistant/.editorconfig b/src/MeoAssistant/.editorconfig index fd6876cbba..0bf4e53aac 100644 --- a/src/MeoAssistant/.editorconfig +++ b/src/MeoAssistant/.editorconfig @@ -5,7 +5,7 @@ root = true indent_style = space insert_final_newline = true charset = utf-8 -end_of_line = crlf +end_of_line = lf [*.json] diff --git a/src/MeoAssistant/AbstractConfiger.cpp b/src/MeoAssistant/AbstractConfiger.cpp index 5840482ecc..a68c181bc8 100644 --- a/src/MeoAssistant/AbstractConfiger.cpp +++ b/src/MeoAssistant/AbstractConfiger.cpp @@ -1,27 +1,27 @@ -#include "AbstractConfiger.h" - -#include - -#include "AsstUtils.hpp" - -bool asst::AbstractConfiger::load(const std::string& filename) -{ - std::string content = utils::load_file_without_bom(filename); - - auto&& ret = json::parser::parse(content); - if (!ret) { - m_last_error = "json pasing error, content:" + content; - return false; - } - - json::value root = std::move(ret.value()); - - try { - return parse(root); - } - catch (json::exception& e) { - m_last_error = std::string("json field error ") + e.what(); - return false; - } - return true; -} +#include "AbstractConfiger.h" + +#include + +#include "AsstUtils.hpp" + +bool asst::AbstractConfiger::load(const std::string& filename) +{ + std::string content = utils::load_file_without_bom(filename); + + auto&& ret = json::parser::parse(content); + if (!ret) { + m_last_error = "json pasing error, content:" + content; + return false; + } + + json::value root = std::move(ret.value()); + + try { + return parse(root); + } + catch (json::exception& e) { + m_last_error = std::string("json field error ") + e.what(); + return false; + } + return true; +} diff --git a/src/MeoAssistant/AbstractConfiger.h b/src/MeoAssistant/AbstractConfiger.h index 6003772054..c3e7a4cb00 100644 --- a/src/MeoAssistant/AbstractConfiger.h +++ b/src/MeoAssistant/AbstractConfiger.h @@ -1,23 +1,23 @@ -#pragma once - -#include "AbstractResource.h" - -#include - -namespace json -{ - class value; -} - -namespace asst -{ - class AbstractConfiger : public AbstractResource - { - public: - virtual ~AbstractConfiger() = default; - virtual bool load(const std::string& filename) override; - - protected: - virtual bool parse(const json::value& json) = 0; - }; -} +#pragma once + +#include "AbstractResource.h" + +#include + +namespace json +{ + class value; +} + +namespace asst +{ + class AbstractConfiger : public AbstractResource + { + public: + virtual ~AbstractConfiger() = default; + virtual bool load(const std::string& filename) override; + + protected: + virtual bool parse(const json::value& json) = 0; + }; +} diff --git a/src/MeoAssistant/AbstractImageAnalyzer.cpp b/src/MeoAssistant/AbstractImageAnalyzer.cpp index 475b828345..c297691e51 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.cpp +++ b/src/MeoAssistant/AbstractImageAnalyzer.cpp @@ -1,97 +1,98 @@ -#include "AbstractImageAnalyzer.h" - -#include "AsstUtils.hpp" -#include "Controller.h" - -asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat image) - : m_image(image), m_roi(empty_rect_to_full(Rect(), image)) -#ifdef LOG_TRACE - , - m_image_draw(image.clone()) -#endif -{} - -asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat image, const Rect& roi) - : m_image(image), - m_roi(empty_rect_to_full(roi, image)) -#ifdef LOG_TRACE - , - m_image_draw(image.clone()) -#endif -{ - ; -} - -void asst::AbstractImageAnalyzer::set_image(const cv::Mat image) -{ - m_image = image; -#ifdef LOG_TRACE - m_image_draw = image.clone(); -#endif -} - -void asst::AbstractImageAnalyzer::set_image(const cv::Mat image, const Rect& roi) -{ - set_image(image); - m_roi = empty_rect_to_full(roi, image); -} - -void asst::AbstractImageAnalyzer::set_roi(const Rect& roi) noexcept -{ - m_roi = empty_rect_to_full(roi, m_image); -} - -void asst::AbstractImageAnalyzer::correct_roi() noexcept -{ - m_roi = Ctrler.shaped_correct(m_roi); -} - -asst::Rect asst::AbstractImageAnalyzer::empty_rect_to_full(const Rect& rect, const cv::Mat image) noexcept -{ - return rect.empty() ? Rect(0, 0, image.cols, image.rows) : rect; -} - -std::string asst::AbstractImageAnalyzer::calc_name_hash() const -{ - return calc_name_hash(m_roi); -} - -std::string asst::AbstractImageAnalyzer::calc_name_hash(const Rect& roi) const -{ - // 从左往右找到第一个白色点 - Rect white_roi = roi; - constexpr static int HashKernelSize = 16; - int threshold = 200; - bool find_point = false; - for (int i = 0; i != white_roi.width && !find_point; ++i) { - for (int j = 0; j != white_roi.height && !find_point; ++j) { - cv::Point point(white_roi.x + i, white_roi.y + j); - auto value = m_image.at(point); - if (value[0] > threshold && value[1] > threshold && value[2] > threshold) { - white_roi.x += i; - white_roi.width -= i; - find_point = true; - break; - } - } - } - cv::Mat image_roi = m_image(utils::make_rect(white_roi)); - cv::Mat bin; - cv::cvtColor(image_roi, image_roi, cv::COLOR_BGR2GRAY); - cv::threshold(image_roi, bin, threshold, 255, cv::THRESH_BINARY); - cv::resize(bin, bin, cv::Size(HashKernelSize, HashKernelSize)); - std::stringstream hash_value; - uchar* pix = bin.data; - int tmp_dec = 0; - for (int ro = 0; ro < 256; ro++) { - tmp_dec = tmp_dec << 1; - if ((bool)*pix) - tmp_dec++; - if (ro % 4 == 3) { - hash_value << std::hex << tmp_dec; - tmp_dec = 0; - } - pix++; - } - return hash_value.str(); -} +#include "AbstractImageAnalyzer.h" + +#include "AsstUtils.hpp" +#include "Controller.h" + +asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat image) + : m_image(image), m_roi(empty_rect_to_full(Rect(), image)) +#ifdef LOG_TRACE + , + m_image_draw(image.clone()) +#endif +{ +} + +asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat image, const Rect& roi) + : m_image(image), + m_roi(empty_rect_to_full(roi, image)) +#ifdef LOG_TRACE + , + m_image_draw(image.clone()) +#endif +{ + ; +} + +void asst::AbstractImageAnalyzer::set_image(const cv::Mat image) +{ + m_image = image; +#ifdef LOG_TRACE + m_image_draw = image.clone(); +#endif +} + +void asst::AbstractImageAnalyzer::set_image(const cv::Mat image, const Rect& roi) +{ + set_image(image); + m_roi = empty_rect_to_full(roi, image); +} + +void asst::AbstractImageAnalyzer::set_roi(const Rect& roi) noexcept +{ + m_roi = empty_rect_to_full(roi, m_image); +} + +void asst::AbstractImageAnalyzer::correct_roi() noexcept +{ + m_roi = Ctrler.shaped_correct(m_roi); +} + +asst::Rect asst::AbstractImageAnalyzer::empty_rect_to_full(const Rect& rect, const cv::Mat image) noexcept +{ + return rect.empty() ? Rect(0, 0, image.cols, image.rows) : rect; +} + +std::string asst::AbstractImageAnalyzer::calc_name_hash() const +{ + return calc_name_hash(m_roi); +} + +std::string asst::AbstractImageAnalyzer::calc_name_hash(const Rect& roi) const +{ + // 从左往右找到第一个白色点 + Rect white_roi = roi; + constexpr static int HashKernelSize = 16; + int threshold = 200; + bool find_point = false; + for (int i = 0; i != white_roi.width && !find_point; ++i) { + for (int j = 0; j != white_roi.height && !find_point; ++j) { + cv::Point point(white_roi.x + i, white_roi.y + j); + auto value = m_image.at(point); + if (value[0] > threshold && value[1] > threshold && value[2] > threshold) { + white_roi.x += i; + white_roi.width -= i; + find_point = true; + break; + } + } + } + cv::Mat image_roi = m_image(utils::make_rect(white_roi)); + cv::Mat bin; + cv::cvtColor(image_roi, image_roi, cv::COLOR_BGR2GRAY); + cv::threshold(image_roi, bin, threshold, 255, cv::THRESH_BINARY); + cv::resize(bin, bin, cv::Size(HashKernelSize, HashKernelSize)); + std::stringstream hash_value; + uchar* pix = bin.data; + int tmp_dec = 0; + for (int ro = 0; ro < 256; ro++) { + tmp_dec = tmp_dec << 1; + if ((bool)*pix) + tmp_dec++; + if (ro % 4 == 3) { + hash_value << std::hex << tmp_dec; + tmp_dec = 0; + } + pix++; + } + return hash_value.str(); +} diff --git a/src/MeoAssistant/AbstractImageAnalyzer.h b/src/MeoAssistant/AbstractImageAnalyzer.h index 3437c7e64b..f29a80738c 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.h +++ b/src/MeoAssistant/AbstractImageAnalyzer.h @@ -1,41 +1,41 @@ -#pragma once - -#include - -#include "AsstDef.h" - -namespace asst -{ - class AbstractImageAnalyzer - { - public: - AbstractImageAnalyzer() = default; - AbstractImageAnalyzer(const cv::Mat image); - AbstractImageAnalyzer(const cv::Mat image, const Rect& roi); - AbstractImageAnalyzer(const AbstractImageAnalyzer&) = delete; - AbstractImageAnalyzer(AbstractImageAnalyzer&&) = delete; - virtual ~AbstractImageAnalyzer() = default; - - virtual void set_image(const cv::Mat image); - virtual void set_image(const cv::Mat image, const Rect& roi); - virtual void set_roi(const Rect& roi) noexcept; - virtual bool analyze() = 0; - virtual void correct_roi() noexcept; - - std::string calc_name_hash() const; // 使用m_roi - std::string calc_name_hash(const Rect& roi) const; // 使用参数roi - - AbstractImageAnalyzer& operator=(const AbstractImageAnalyzer&) = delete; - AbstractImageAnalyzer& operator=(AbstractImageAnalyzer&&) = delete; - - protected: - static Rect empty_rect_to_full(const Rect& rect, const cv::Mat image) noexcept; - - cv::Mat m_image; - Rect m_roi; - -#ifdef LOG_TRACE - cv::Mat m_image_draw; -#endif - }; -} +#pragma once + +#include + +#include "AsstDef.h" + +namespace asst +{ + class AbstractImageAnalyzer + { + public: + AbstractImageAnalyzer() = default; + AbstractImageAnalyzer(const cv::Mat image); + AbstractImageAnalyzer(const cv::Mat image, const Rect& roi); + AbstractImageAnalyzer(const AbstractImageAnalyzer&) = delete; + AbstractImageAnalyzer(AbstractImageAnalyzer&&) = delete; + virtual ~AbstractImageAnalyzer() = default; + + virtual void set_image(const cv::Mat image); + virtual void set_image(const cv::Mat image, const Rect& roi); + virtual void set_roi(const Rect& roi) noexcept; + virtual bool analyze() = 0; + virtual void correct_roi() noexcept; + + std::string calc_name_hash() const; // 使用m_roi + std::string calc_name_hash(const Rect& roi) const; // 使用参数roi + + AbstractImageAnalyzer& operator=(const AbstractImageAnalyzer&) = delete; + AbstractImageAnalyzer& operator=(AbstractImageAnalyzer&&) = delete; + + protected: + static Rect empty_rect_to_full(const Rect& rect, const cv::Mat image) noexcept; + + cv::Mat m_image; + Rect m_roi; + +#ifdef LOG_TRACE + cv::Mat m_image_draw; +#endif + }; +} diff --git a/src/MeoAssistant/AbstractResource.h b/src/MeoAssistant/AbstractResource.h index 78f9d1ca14..6c7e84e8b7 100644 --- a/src/MeoAssistant/AbstractResource.h +++ b/src/MeoAssistant/AbstractResource.h @@ -1,28 +1,28 @@ -#pragma once - -#include - -namespace asst -{ - class AbstractResource - { - public: - AbstractResource() = default; - AbstractResource(const AbstractResource& rhs) = delete; - AbstractResource(AbstractResource&& rhs) noexcept = delete; - - virtual ~AbstractResource() = default; - - virtual bool load(const std::string& filename) = 0; - virtual const std::string& get_last_error() const noexcept - { - return m_last_error; - } - - AbstractResource& operator=(const AbstractResource& rhs) = delete; - AbstractResource& operator=(AbstractResource&& rhs) noexcept = delete; - - protected: - std::string m_last_error; - }; -} +#pragma once + +#include + +namespace asst +{ + class AbstractResource + { + public: + AbstractResource() = default; + AbstractResource(const AbstractResource& rhs) = delete; + AbstractResource(AbstractResource&& rhs) noexcept = delete; + + virtual ~AbstractResource() = default; + + virtual bool load(const std::string& filename) = 0; + virtual const std::string& get_last_error() const noexcept + { + return m_last_error; + } + + AbstractResource& operator=(const AbstractResource& rhs) = delete; + AbstractResource& operator=(AbstractResource&& rhs) noexcept = delete; + + protected: + std::string m_last_error; + }; +} diff --git a/src/MeoAssistant/AbstractTask.cpp b/src/MeoAssistant/AbstractTask.cpp index 1834c62a89..4ddffcd09c 100644 --- a/src/MeoAssistant/AbstractTask.cpp +++ b/src/MeoAssistant/AbstractTask.cpp @@ -1,99 +1,99 @@ -#include "AbstractTask.h" - -#include -#include -#include - -#include - -#include "AsstUtils.hpp" -#include "Controller.h" -#include "Logger.hpp" -#include "Resource.h" - -using namespace asst; - -AbstractTask::AbstractTask(AsstCallback callback, void* callback_arg) - : m_callback(callback), - m_callback_arg(callback_arg) -{ - ; -} - -bool asst::AbstractTask::run() -{ - for (m_cur_retry = 0; m_cur_retry < m_retry_times; ++m_cur_retry) { - if (_run()) { - return true; - } - if (need_exit()) { - return false; - } - int delay = Resrc.cfg().get_options().task_delay; - sleep(delay); - - if (!on_run_fails()) { - return false; - } - } - return false; -} - -bool AbstractTask::sleep(unsigned millisecond) -{ - if (need_exit()) { - return false; - } - if (millisecond == 0) { - return true; - } - auto start = std::chrono::steady_clock::now(); - long long duration = 0; - - json::value callback_json; - callback_json["time"] = millisecond; - m_callback(AsstMsg::ReadyToSleep, callback_json, m_callback_arg); - - while (!need_exit() && duration < millisecond) { - duration = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start).count(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - std::this_thread::yield(); - } - m_callback(AsstMsg::EndOfSleep, callback_json, m_callback_arg); - - return !need_exit(); -} - -bool AbstractTask::save_image(const cv::Mat image, const std::string& dir) -{ - std::filesystem::create_directory(dir); - const std::string time_str = utils::string_replace_all(utils::string_replace_all(utils::get_format_time(), " ", "_"), ":", "-"); - const std::string filename = dir + time_str + ".png"; - - bool ret = cv::imwrite(filename, image); - - json::value callback_json; - callback_json["filename"] = filename; - callback_json["ret"] = ret; - callback_json["offset"] = 0; - m_callback(AsstMsg::PrintWindow, callback_json, m_callback_arg); - - return true; -} - -bool asst::AbstractTask::need_exit() const -{ - return m_exit_flag != NULL && *m_exit_flag == true; -} - -void asst::AbstractTask::click_return_button() -{ - LogTraceFunction; - const auto return_task_ptr = Task.get("Return"); - - Rect ReturnButtonRect = return_task_ptr->specific_rect; - - Ctrler.click(ReturnButtonRect); - sleep(return_task_ptr->rear_delay); -} +#include "AbstractTask.h" + +#include +#include +#include + +#include + +#include "AsstUtils.hpp" +#include "Controller.h" +#include "Logger.hpp" +#include "Resource.h" + +using namespace asst; + +AbstractTask::AbstractTask(AsstCallback callback, void* callback_arg) + : m_callback(callback), + m_callback_arg(callback_arg) +{ + ; +} + +bool asst::AbstractTask::run() +{ + for (m_cur_retry = 0; m_cur_retry < m_retry_times; ++m_cur_retry) { + if (_run()) { + return true; + } + if (need_exit()) { + return false; + } + int delay = Resrc.cfg().get_options().task_delay; + sleep(delay); + + if (!on_run_fails()) { + return false; + } + } + return false; +} + +bool AbstractTask::sleep(unsigned millisecond) +{ + if (need_exit()) { + return false; + } + if (millisecond == 0) { + return true; + } + auto start = std::chrono::steady_clock::now(); + long long duration = 0; + + json::value callback_json; + callback_json["time"] = millisecond; + m_callback(AsstMsg::ReadyToSleep, callback_json, m_callback_arg); + + while (!need_exit() && duration < millisecond) { + duration = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::yield(); + } + m_callback(AsstMsg::EndOfSleep, callback_json, m_callback_arg); + + return !need_exit(); +} + +bool AbstractTask::save_image(const cv::Mat image, const std::string& dir) +{ + std::filesystem::create_directory(dir); + const std::string time_str = utils::string_replace_all(utils::string_replace_all(utils::get_format_time(), " ", "_"), ":", "-"); + const std::string filename = dir + time_str + ".png"; + + bool ret = cv::imwrite(filename, image); + + json::value callback_json; + callback_json["filename"] = filename; + callback_json["ret"] = ret; + callback_json["offset"] = 0; + m_callback(AsstMsg::PrintWindow, callback_json, m_callback_arg); + + return true; +} + +bool asst::AbstractTask::need_exit() const +{ + return m_exit_flag != NULL && *m_exit_flag == true; +} + +void asst::AbstractTask::click_return_button() +{ + LogTraceFunction; + const auto return_task_ptr = Task.get("Return"); + + Rect ReturnButtonRect = return_task_ptr->specific_rect; + + Ctrler.click(ReturnButtonRect); + sleep(return_task_ptr->rear_delay); +} diff --git a/src/MeoAssistant/AipOcr.cpp b/src/MeoAssistant/AipOcr.cpp index 5dfe771aed..00383c79af 100644 --- a/src/MeoAssistant/AipOcr.cpp +++ b/src/MeoAssistant/AipOcr.cpp @@ -1,152 +1,152 @@ -#include "AipOcr.h" - -#include - -#include -#include -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" -#include "Resource.h" - -bool asst::AipOcr::request_access_token(const std::string& client_id, const std::string& client_secret) -{ - LogTraceFunction; - - m_access_token.clear(); - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s")"; - - constexpr int cmd_len = 256; - char cmd[cmd_len] = { 0 }; -#ifdef _MSC_VER - sprintf_s(cmd, cmd_len, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); -#else - sprintf(cmd, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); -#endif - - Log.trace("call cmd:", cmd); - - std::string response = utils::callcmd(cmd); - - Log.trace("response:", response); - - auto parse_res = json::parse(response); - if (!parse_res) { - return false; - } - try { - auto json = parse_res.value(); - m_access_token = json.at("access_token").as_string(); - } - catch (...) { - return false; - } - return true; -} - -bool asst::AipOcr::request_ocr_general(const cv::Mat image, std::vector& out_result, const TextRectProc& pred) -{ - LogTraceFunction; - - if (m_access_token.empty()) { - auto& cfg = Resrc.cfg().get_options().aip_ocr; - request_access_token(cfg.client_id, cfg.client_secret); - } - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/general?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; - - return request_ocr_and_parse(cmd_fmt, image, out_result, pred); -} - -bool asst::AipOcr::request_ocr_accurate(const cv::Mat image, std::vector& out_result, const TextRectProc& pred) -{ - LogTraceFunction; - - if (m_access_token.empty()) { - auto& cfg = Resrc.cfg().get_options().aip_ocr; - request_access_token(cfg.client_id, cfg.client_secret); - } - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; - - return request_ocr_and_parse(cmd_fmt, image, out_result, pred); -} - -bool asst::AipOcr::request_ocr_and_parse(std::string_view cmd_fmt, const cv::Mat image, std::vector& out_result, const TextRectProc& pred) -{ - if (m_access_token.empty()) { - return false; - } - // CreateProcess 最大只能接受 32767 chars,一张图片随便就超过了 - // 这个功能暂时没法用,得集成下 libcurl 或者别的 - std::vector buf; - cv::imencode(".png", image, buf); - auto* enc_msg = reinterpret_cast(buf.data()); - std::string encoded = base64_encode(enc_msg, buf.size()); - - size_t cmd_len = encoded.size() + cmd_fmt.size() + m_access_token.size(); - char* cmd = new char[cmd_len]; - memset(cmd, 0, cmd_len); -#ifdef _MSC_VER - sprintf_s(cmd, cmd_len, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); -#else - sprintf(cmd, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); -#endif - Log.trace("call cmd:", cmd); - std::string response = utils::callcmd(cmd); - delete[] cmd; - Log.trace("response:", response); - - auto parse_res = json::parse(response); - if (!parse_res) { - return false; - } - try { - auto json = parse_res.value(); - return parse_response(json, out_result, pred); - } - catch (...) { - return false; - } -} - -bool asst::AipOcr::parse_response(const json::value& json, std::vector& out_result, const TextRectProc& pred) -{ - if (!json.exist("words_result")) { - return false; - } - - std::vector result; - std::string log_str_raw; - std::string log_str_proc; - for (const auto& word : json.at("words_result").as_array()) { - std::string text = word.at("words").as_string(); - - auto& loc = word.at("location").as_object(); - int left = loc.at("left").as_integer(); - int right = loc.at("right").as_integer(); - int top = loc.at("top").as_integer(); - int bottom = loc.at("bottom").as_integer(); - - Rect rect(left, top, right - left, bottom - top); - - TextRect tr{ text, rect, 0 }; - - log_str_raw += tr.to_string() + ", "; - if (!pred || pred(tr)) { - log_str_proc += tr.to_string() + ", "; - result.emplace_back(std::move(tr)); - } - } - - Log.trace("AipOcr::parse_response | raw : ", log_str_raw); - Log.trace("AipOcr::parse_response | proc : ", log_str_proc); - - out_result = result; - return true; -} +#include "AipOcr.h" + +#include + +#include +#include +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" +#include "Resource.h" + +bool asst::AipOcr::request_access_token(const std::string& client_id, const std::string& client_secret) +{ + LogTraceFunction; + + m_access_token.clear(); + + std::string_view cmd_fmt = + R"(curl -s -k "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s")"; + + constexpr int cmd_len = 256; + char cmd[cmd_len] = { 0 }; +#ifdef _MSC_VER + sprintf_s(cmd, cmd_len, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); +#else + sprintf(cmd, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); +#endif + + Log.trace("call cmd:", cmd); + + std::string response = utils::callcmd(cmd); + + Log.trace("response:", response); + + auto parse_res = json::parse(response); + if (!parse_res) { + return false; + } + try { + auto json = parse_res.value(); + m_access_token = json.at("access_token").as_string(); + } + catch (...) { + return false; + } + return true; +} + +bool asst::AipOcr::request_ocr_general(const cv::Mat image, std::vector& out_result, const TextRectProc& pred) +{ + LogTraceFunction; + + if (m_access_token.empty()) { + auto& cfg = Resrc.cfg().get_options().aip_ocr; + request_access_token(cfg.client_id, cfg.client_secret); + } + + std::string_view cmd_fmt = + R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/general?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; + + return request_ocr_and_parse(cmd_fmt, image, out_result, pred); +} + +bool asst::AipOcr::request_ocr_accurate(const cv::Mat image, std::vector& out_result, const TextRectProc& pred) +{ + LogTraceFunction; + + if (m_access_token.empty()) { + auto& cfg = Resrc.cfg().get_options().aip_ocr; + request_access_token(cfg.client_id, cfg.client_secret); + } + + std::string_view cmd_fmt = + R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; + + return request_ocr_and_parse(cmd_fmt, image, out_result, pred); +} + +bool asst::AipOcr::request_ocr_and_parse(std::string_view cmd_fmt, const cv::Mat image, std::vector& out_result, const TextRectProc& pred) +{ + if (m_access_token.empty()) { + return false; + } + // CreateProcess 最大只能接受 32767 chars,一张图片随便就超过了 + // 这个功能暂时没法用,得集成下 libcurl 或者别的 + std::vector buf; + cv::imencode(".png", image, buf); + auto* enc_msg = reinterpret_cast(buf.data()); + std::string encoded = base64_encode(enc_msg, buf.size()); + + size_t cmd_len = encoded.size() + cmd_fmt.size() + m_access_token.size(); + char* cmd = new char[cmd_len]; + memset(cmd, 0, cmd_len); +#ifdef _MSC_VER + sprintf_s(cmd, cmd_len, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); +#else + sprintf(cmd, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); +#endif + Log.trace("call cmd:", cmd); + std::string response = utils::callcmd(cmd); + delete[] cmd; + Log.trace("response:", response); + + auto parse_res = json::parse(response); + if (!parse_res) { + return false; + } + try { + auto json = parse_res.value(); + return parse_response(json, out_result, pred); + } + catch (...) { + return false; + } +} + +bool asst::AipOcr::parse_response(const json::value& json, std::vector& out_result, const TextRectProc& pred) +{ + if (!json.exist("words_result")) { + return false; + } + + std::vector result; + std::string log_str_raw; + std::string log_str_proc; + for (const auto& word : json.at("words_result").as_array()) { + std::string text = word.at("words").as_string(); + + auto& loc = word.at("location").as_object(); + int left = loc.at("left").as_integer(); + int right = loc.at("right").as_integer(); + int top = loc.at("top").as_integer(); + int bottom = loc.at("bottom").as_integer(); + + Rect rect(left, top, right - left, bottom - top); + + TextRect tr{ text, rect, 0 }; + + log_str_raw += tr.to_string() + ", "; + if (!pred || pred(tr)) { + log_str_proc += tr.to_string() + ", "; + result.emplace_back(std::move(tr)); + } + } + + Log.trace("AipOcr::parse_response | raw : ", log_str_raw); + Log.trace("AipOcr::parse_response | proc : ", log_str_proc); + + out_result = result; + return true; +} diff --git a/src/MeoAssistant/Assistant.cpp b/src/MeoAssistant/Assistant.cpp index 42b1d232a6..a5b39d22e4 100644 --- a/src/MeoAssistant/Assistant.cpp +++ b/src/MeoAssistant/Assistant.cpp @@ -1,651 +1,651 @@ -#include "Assistant.h" - -#include -#include - -#include -#include - -#include "AsstUtils.hpp" -#include "Controller.h" -#include "Logger.hpp" -#include "Resource.h" - -#include "CreditShoppingTask.h" -#include "InfrastDormTask.h" -#include "InfrastInfoTask.h" -#include "InfrastMfgTask.h" -#include "InfrastOfficeTask.h" -#include "InfrastPowerTask.h" -#include "InfrastReceptionTask.h" -#include "InfrastTradeTask.h" -#include "ProcessTask.h" -#include "RecruitTask.h" -#include "AutoRecruitTask.h" -#include "InfrastControlTask.h" -#include "RuntimeStatus.h" - -using namespace asst; - -Assistant::Assistant(std::string dirname, AsstCallback callback, void* callback_arg) - : m_dirname(std::move(dirname) + "/"), - m_callback(callback), - m_callback_arg(callback_arg) -{ - Logger::set_dirname(m_dirname); - Controller::set_dirname(m_dirname); - - LogTraceFunction; - - bool resource_ret = Resrc.load(m_dirname + "Resource/"); - if (!resource_ret) { - const std::string& error = Resrc.get_last_error(); - Log.error("resource broken:", error); - if (m_callback == nullptr) { - throw error; - } - json::value callback_json; - callback_json["type"] = "resource broken"; - callback_json["what"] = error; - m_callback(AsstMsg::InitFaild, callback_json, m_callback_arg); - throw error; - } - - m_working_thread = std::thread(std::bind(&Assistant::working_proc, this)); - m_msg_thread = std::thread(std::bind(&Assistant::msg_proc, this)); -} - -Assistant::~Assistant() -{ - LogTraceFunction; - - m_thread_exit = true; - m_thread_idle = true; - m_condvar.notify_all(); - m_msg_condvar.notify_all(); - - if (m_working_thread.joinable()) { - m_working_thread.join(); - } - if (m_msg_thread.joinable()) { - m_msg_thread.join(); - } -} - -bool asst::Assistant::catch_default() -{ - LogTraceFunction; - - auto& opt = Resrc.cfg().get_options(); - switch (opt.connect_type) { - case ConnectType::Emulator: - return catch_emulator(); - case ConnectType::Custom: - return catch_custom(); - default: - return false; - } -} - -bool Assistant::catch_emulator(const std::string& emulator_name) -{ - LogTraceFunction; - - stop(); -#ifdef _WIN32 - bool ret = false; - //std::string cor_name = emulator_name; - auto& cfg = Resrc.cfg(); - - std::unique_lock lock(m_mutex); - - // 自动匹配模拟器,逐个找 - if (emulator_name.empty()) { - for (const auto& [name, info] : cfg.get_emulators_info()) { - ret = Ctrler.try_capture(info); - if (ret) { - //cor_name = name; - break; - } - } - } - else { // 指定的模拟器 - auto& info = cfg.get_emulators_info().at(emulator_name); - ret = Ctrler.try_capture(info); - } - - m_inited = ret; - return ret; -#else // Not supported catch emulator in Linux - return false; -#endif -} - -bool asst::Assistant::catch_custom(const std::string& address) -{ - LogTraceFunction; - - stop(); - - bool ret = false; - auto& cfg = Resrc.cfg(); - - std::unique_lock lock(m_mutex); - - EmulatorInfo remote_info = cfg.get_emulators_info().at("Custom"); - if (!address.empty()) { - remote_info.adb.addresses.push_back(address); - } - - ret = Ctrler.try_capture(remote_info, true); - - m_inited = ret; - return ret; -} - -bool asst::Assistant::catch_fake() -{ - LogTraceFunction; - - stop(); - - m_inited = true; - return true; -} - -bool asst::Assistant::append_start_up(bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - auto task_ptr = std::make_shared(task_callback, (void*)this); - task_ptr->set_task_chain("StartUp"); - task_ptr->set_tasks({ "StartUp" }); - task_ptr->set_times_limit("ReturnToTerminal", 0); - task_ptr->set_times_limit("Terminal", 0); - - m_tasks_queue.emplace(task_ptr); - - if (!only_append) { - return start(false); - } - - return true; -} - -bool asst::Assistant::append_fight(const std::string& stage, int mecidine, int stone, int times, bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - constexpr const char* TaskChain = "Fight"; - - // 进入选关界面(主界面的“终端”点进去) - auto terminal_task_ptr = std::make_shared(task_callback, (void*)this); - terminal_task_ptr->set_task_chain(TaskChain); - terminal_task_ptr->set_tasks({ "StageBegin" }); - terminal_task_ptr->set_times_limit("LastBattle", 0); - terminal_task_ptr->set_times_limit("StartButton1", 0); - terminal_task_ptr->set_times_limit("StartButton2", 0); - terminal_task_ptr->set_times_limit("MedicineConfirm", 0); - terminal_task_ptr->set_times_limit("StoneConfirm", 0); - - // 进入对应的关卡 - auto stage_task_ptr = std::make_shared(task_callback, (void*)this); - stage_task_ptr->set_task_chain(TaskChain); - stage_task_ptr->set_tasks({ stage }); - stage_task_ptr->set_times_limit("StartButton1", 0); - stage_task_ptr->set_times_limit("StartButton2", 0); - stage_task_ptr->set_times_limit("MedicineConfirm", 0); - stage_task_ptr->set_times_limit("StoneConfirm", 0); - - // 开始战斗任务 - auto fight_task_ptr = std::make_shared(task_callback, (void*)this); - fight_task_ptr->set_task_chain(TaskChain); - fight_task_ptr->set_tasks({ "FightBegin" }); - fight_task_ptr->set_times_limit("MedicineConfirm", mecidine); - fight_task_ptr->set_times_limit("StoneConfirm", stone); - fight_task_ptr->set_times_limit("StartButton1", times); - fight_task_ptr->set_times_limit("StartButton2", times); - - std::unique_lock lock(m_mutex); - - if (!stage.empty()) { - m_tasks_queue.emplace(terminal_task_ptr); - m_tasks_queue.emplace(stage_task_ptr); - } - m_tasks_queue.emplace(fight_task_ptr); - - if (!only_append) { - return start(false); - } - - return true; -} - -bool asst::Assistant::append_award(bool only_append) -{ - return append_process_task("AwardBegin", "Award"); -} - -bool asst::Assistant::append_visit(bool only_append) -{ - return append_process_task("VisitBegin", "Visit"); -} - -bool asst::Assistant::append_mall(bool with_shopping, bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - const std::string task_chain = "Mall"; - - append_process_task("MallBegin", task_chain); - - if (with_shopping) { - auto shopping_task_ptr = std::make_shared(task_callback, (void*)this); - shopping_task_ptr->set_task_chain(task_chain); - m_tasks_queue.emplace(shopping_task_ptr); - } - - if (!only_append) { - return start(false); - } - - return true; -} - -bool Assistant::append_process_task(const std::string& task_name, std::string task_chain, int retry_times) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - //std::unique_lock lock(m_mutex); - - if (task_chain.empty()) { - task_chain = task_name; - } - - auto task_ptr = std::make_shared(task_callback, (void*)this); - task_ptr->set_task_chain(task_chain); - task_ptr->set_tasks({ task_name }); - task_ptr->set_retry_times(retry_times); - - m_tasks_queue.emplace(task_ptr); - - //if (!only_append) { - // return start(false); - //} - - return true; -} - -bool asst::Assistant::append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - static const std::string TaskChain = "Recruit"; - - append_process_task("RecruitBegin", TaskChain); - - auto recruit_task_ptr = std::make_shared(task_callback, (void*)this); - recruit_task_ptr->set_max_times(max_times); - recruit_task_ptr->set_need_refresh(need_refresh); - recruit_task_ptr->set_select_level(select_level); - recruit_task_ptr->set_confirm_level(confirm_level); - recruit_task_ptr->set_task_chain(TaskChain); - recruit_task_ptr->set_retry_times(AutoRecruitTaskRetryTimesDefault); - - m_tasks_queue.emplace(recruit_task_ptr); - - return true; -} - -#ifdef LOG_TRACE -bool Assistant::append_debug() -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - { - constexpr static const char* DebugTaskChain = "Debug"; - auto shift_task_ptr = std::make_shared(task_callback, (void*)this); - shift_task_ptr->set_work_mode(infrast::WorkMode::Aggressive); - shift_task_ptr->set_facility("Control"); - shift_task_ptr->set_product("MoodAddition"); - shift_task_ptr->set_task_chain(DebugTaskChain); - m_tasks_queue.emplace(shift_task_ptr); - } - - return true; -} -#endif - -bool Assistant::start_recruit_calc(const std::vector& select_level, bool set_time) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - std::unique_lock lock(m_mutex); - - auto task_ptr = std::make_shared(task_callback, (void*)this); - task_ptr->set_param(select_level, set_time); - task_ptr->set_retry_times(OpenRecruitTaskRetryTimesDefault); - task_ptr->set_task_chain("RecruitCalc"); - m_tasks_queue.emplace(task_ptr); - - return start(false); -} - -bool asst::Assistant::append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append) -{ - LogTraceFunction; - if (!m_inited) { - return false; - } - - // 保留接口,目前强制按激进模式进行换班 - work_mode = infrast::WorkMode::Aggressive; - - constexpr static const char* InfrastTaskCahin = "Infrast"; - - // 这个流程任务,结束的时候是处于基建主界面的。既可以用于进入基建,也可以用于从设施里返回基建主界面 - - auto append_infrast_begin = [&]() { - append_process_task("InfrastBegin", InfrastTaskCahin); - }; - - append_infrast_begin(); - - auto info_task_ptr = std::make_shared(task_callback, (void*)this); - info_task_ptr->set_work_mode(work_mode); - info_task_ptr->set_task_chain(InfrastTaskCahin); - info_task_ptr->set_mood_threshold(dorm_threshold); - - m_tasks_queue.emplace(info_task_ptr); - - // 因为后期要考虑多任务间的联动等,所以这些任务的声明暂时不放到for循环中 - auto mfg_task_ptr = std::make_shared(task_callback, (void*)this); - mfg_task_ptr->set_work_mode(work_mode); - mfg_task_ptr->set_task_chain(InfrastTaskCahin); - mfg_task_ptr->set_mood_threshold(dorm_threshold); - mfg_task_ptr->set_uses_of_drone(uses_of_drones); - auto trade_task_ptr = std::make_shared(task_callback, (void*)this); - trade_task_ptr->set_work_mode(work_mode); - trade_task_ptr->set_task_chain(InfrastTaskCahin); - trade_task_ptr->set_mood_threshold(dorm_threshold); - trade_task_ptr->set_uses_of_drone(uses_of_drones); - auto power_task_ptr = std::make_shared(task_callback, (void*)this); - power_task_ptr->set_work_mode(work_mode); - power_task_ptr->set_task_chain(InfrastTaskCahin); - power_task_ptr->set_mood_threshold(dorm_threshold); - auto office_task_ptr = std::make_shared(task_callback, (void*)this); - office_task_ptr->set_work_mode(work_mode); - office_task_ptr->set_task_chain(InfrastTaskCahin); - office_task_ptr->set_mood_threshold(dorm_threshold); - auto recpt_task_ptr = std::make_shared(task_callback, (void*)this); - recpt_task_ptr->set_work_mode(work_mode); - recpt_task_ptr->set_task_chain(InfrastTaskCahin); - recpt_task_ptr->set_mood_threshold(dorm_threshold); - auto control_task_ptr = std::make_shared(task_callback, (void*)this); - control_task_ptr->set_work_mode(work_mode); - control_task_ptr->set_task_chain(InfrastTaskCahin); - control_task_ptr->set_mood_threshold(dorm_threshold); - - auto dorm_task_ptr = std::make_shared(task_callback, (void*)this); - dorm_task_ptr->set_work_mode(work_mode); - dorm_task_ptr->set_task_chain(InfrastTaskCahin); - dorm_task_ptr->set_mood_threshold(dorm_threshold); - - for (const auto& facility : order) { - if (facility == "Dorm") { - m_tasks_queue.emplace(dorm_task_ptr); - } - else if (facility == "Mfg") { - m_tasks_queue.emplace(mfg_task_ptr); - } - else if (facility == "Trade") { - m_tasks_queue.emplace(trade_task_ptr); - } - else if (facility == "Power") { - m_tasks_queue.emplace(power_task_ptr); - } - else if (facility == "Office") { - m_tasks_queue.emplace(office_task_ptr); - } - else if (facility == "Reception") { - m_tasks_queue.emplace(recpt_task_ptr); - } - else if (facility == "Control") { - m_tasks_queue.emplace(control_task_ptr); - } - else { - Log.error("append_infrast | Unknown facility", facility); - } - append_infrast_begin(); - } - - if (!only_append) { - return start(false); - } - - return true; -} - -void asst::Assistant::set_penguin_id(const std::string& id) -{ - auto& opt = Resrc.cfg().get_options(); - if (id.empty()) { - opt.penguin_report.extra_param.clear(); - } - else { - opt.penguin_report.extra_param = "-H \"authorization: PenguinID " + id + "\""; - } -} - -bool asst::Assistant::start(bool block) -{ - LogTraceFunction; - Log.trace("Start |", block ? "block" : "non block"); - - if (!m_thread_idle || !m_inited) { - return false; - } - std::unique_lock lock; - if (block) { // 外部调用 - lock = std::unique_lock(m_mutex); - } - - m_thread_idle = false; - m_condvar.notify_one(); - - return true; -} - -bool Assistant::stop(bool block) -{ - LogTraceFunction; - Log.trace("Stop |", block ? "block" : "non block"); - - m_thread_idle = true; - - std::unique_lock lock; - if (block) { // 外部调用 - lock = std::unique_lock(m_mutex); - } - decltype(m_tasks_queue) empty; - m_tasks_queue.swap(empty); - - clear_cache(); - - return true; -} - -void Assistant::working_proc() -{ - LogTraceFunction; - - std::string pre_taskchain; - while (!m_thread_exit) { - //LogTraceScope("Assistant::working_proc Loop"); - std::unique_lock lock(m_mutex); - - if (!m_thread_idle && !m_tasks_queue.empty()) { - auto start_time = std::chrono::steady_clock::now(); - - auto task_ptr = m_tasks_queue.front(); - - std::string cur_taskchain = task_ptr->get_task_chain(); - json::value task_json = json::object{ - {"task_chain", cur_taskchain}, - }; - - if (cur_taskchain != pre_taskchain) { - task_callback(AsstMsg::TaskChainStart, task_json, this); - pre_taskchain = cur_taskchain; - } - - task_ptr->set_exit_flag(&m_thread_idle); - bool ret = task_ptr->run(); - m_tasks_queue.pop(); - - if (!ret) { - task_callback(AsstMsg::TaskError, task_json, this); - } - else if (m_tasks_queue.empty() || cur_taskchain != m_tasks_queue.front()->get_task_chain()) { - task_callback(AsstMsg::TaskChainCompleted, task_json, this); - } - if (m_tasks_queue.empty()) { - task_callback(AsstMsg::AllTasksCompleted, task_json, this); - } - - //clear_cache(); - - auto& delay = Resrc.cfg().get_options().task_delay; - m_condvar.wait_until(lock, start_time + std::chrono::milliseconds(delay), - [&]() -> bool { return m_thread_idle; }); - } - else { - pre_taskchain.clear(); - m_thread_idle = true; - Log.flush(); - //controller.set_idle(true); - m_condvar.wait(lock); - } - } -} - -void Assistant::msg_proc() -{ - LogTraceFunction; - - while (!m_thread_exit) { - //LogTraceScope("Assistant::msg_proc Loop"); - std::unique_lock lock(m_msg_mutex); - if (!m_msg_queue.empty()) { - // 结构化绑定只能是引用,后续的pop会使引用失效,所以需要重新构造一份,这里采用了move的方式 - auto&& [temp_msg, temp_detail] = m_msg_queue.front(); - AsstMsg msg = std::move(temp_msg); - json::value detail = std::move(temp_detail); - m_msg_queue.pop(); - lock.unlock(); - - if (m_callback) { - m_callback(msg, detail, m_callback_arg); - } - } - else { - m_msg_condvar.wait(lock); - } - } -} - -void Assistant::task_callback(AsstMsg msg, const json::value& detail, void* custom_arg) -{ - Log.trace("Assistant::task_callback |", msg, detail.to_string()); - - Assistant* p_this = (Assistant*)custom_arg; - json::value more_detail = detail; - switch (msg) { - case AsstMsg::PtrIsNull: - case AsstMsg::ImageIsEmpty: - p_this->stop(false); - break; - case AsstMsg::StageDrops: - more_detail = p_this->organize_stage_drop(more_detail); - break; - default: - break; - } - - // Todo: 有些不需要回调给外部的消息,得在这里给拦截掉 - // 加入回调消息队列,由回调消息线程外抛给外部 - p_this->append_callback(msg, std::move(more_detail)); -} - -void asst::Assistant::append_callback(AsstMsg msg, json::value detail) -{ - std::unique_lock lock(m_msg_mutex); - m_msg_queue.emplace(msg, std::move(detail)); - m_msg_condvar.notify_one(); -} - -void Assistant::clear_cache() -{ - Status.clear(); - Resrc.item().clear_drop_count(); - //Task.clear_cache(); -} - -json::value asst::Assistant::organize_stage_drop(const json::value& rec) -{ - json::value dst = rec; - auto& item = Resrc.item(); - for (json::value& drop : dst["drops"].as_array()) { - std::string id = drop["itemId"].as_string(); - int quantity = drop["quantity"].as_integer(); - item.increase_drop_count(id, quantity); - const std::string& name = item.get_item_name(id); - drop["itemName"] = name.empty() ? "未知材料" : name; - } - std::vector statistics_vec; - for (auto&& [id, count] : item.get_drop_count()) { - json::value info; - info["itemId"] = id; - const std::string& name = item.get_item_name(id); - info["itemName"] = name.empty() ? "未知材料" : name; - info["count"] = count; - statistics_vec.emplace_back(std::move(info)); - } - //// 排个序,数量多的放前面 - //std::sort(statistics_vec.begin(), statistics_vec.end(), - // [](const json::value& lhs, const json::value& rhs) -> bool { - // return lhs.at("count").as_integer() > rhs.at("count").as_integer(); - // }); - - dst["statistics"] = json::array(std::move(statistics_vec)); - - Log.trace("organize_stage_drop | ", dst.to_string()); - - return dst; -} +#include "Assistant.h" + +#include +#include + +#include +#include + +#include "AsstUtils.hpp" +#include "Controller.h" +#include "Logger.hpp" +#include "Resource.h" + +#include "CreditShoppingTask.h" +#include "InfrastDormTask.h" +#include "InfrastInfoTask.h" +#include "InfrastMfgTask.h" +#include "InfrastOfficeTask.h" +#include "InfrastPowerTask.h" +#include "InfrastReceptionTask.h" +#include "InfrastTradeTask.h" +#include "ProcessTask.h" +#include "RecruitTask.h" +#include "AutoRecruitTask.h" +#include "InfrastControlTask.h" +#include "RuntimeStatus.h" + +using namespace asst; + +Assistant::Assistant(std::string dirname, AsstCallback callback, void* callback_arg) + : m_dirname(std::move(dirname) + "/"), + m_callback(callback), + m_callback_arg(callback_arg) +{ + Logger::set_dirname(m_dirname); + Controller::set_dirname(m_dirname); + + LogTraceFunction; + + bool resource_ret = Resrc.load(m_dirname + "Resource/"); + if (!resource_ret) { + const std::string& error = Resrc.get_last_error(); + Log.error("resource broken:", error); + if (m_callback == nullptr) { + throw error; + } + json::value callback_json; + callback_json["type"] = "resource broken"; + callback_json["what"] = error; + m_callback(AsstMsg::InitFaild, callback_json, m_callback_arg); + throw error; + } + + m_working_thread = std::thread(std::bind(&Assistant::working_proc, this)); + m_msg_thread = std::thread(std::bind(&Assistant::msg_proc, this)); +} + +Assistant::~Assistant() +{ + LogTraceFunction; + + m_thread_exit = true; + m_thread_idle = true; + m_condvar.notify_all(); + m_msg_condvar.notify_all(); + + if (m_working_thread.joinable()) { + m_working_thread.join(); + } + if (m_msg_thread.joinable()) { + m_msg_thread.join(); + } +} + +bool asst::Assistant::catch_default() +{ + LogTraceFunction; + + auto& opt = Resrc.cfg().get_options(); + switch (opt.connect_type) { + case ConnectType::Emulator: + return catch_emulator(); + case ConnectType::Custom: + return catch_custom(); + default: + return false; + } +} + +bool Assistant::catch_emulator(const std::string& emulator_name) +{ + LogTraceFunction; + + stop(); +#ifdef _WIN32 + bool ret = false; + //std::string cor_name = emulator_name; + auto& cfg = Resrc.cfg(); + + std::unique_lock lock(m_mutex); + + // 自动匹配模拟器,逐个找 + if (emulator_name.empty()) { + for (const auto& [name, info] : cfg.get_emulators_info()) { + ret = Ctrler.try_capture(info); + if (ret) { + //cor_name = name; + break; + } + } + } + else { // 指定的模拟器 + auto& info = cfg.get_emulators_info().at(emulator_name); + ret = Ctrler.try_capture(info); + } + + m_inited = ret; + return ret; +#else // Not supported catch emulator in Linux + return false; +#endif +} + +bool asst::Assistant::catch_custom(const std::string& address) +{ + LogTraceFunction; + + stop(); + + bool ret = false; + auto& cfg = Resrc.cfg(); + + std::unique_lock lock(m_mutex); + + EmulatorInfo remote_info = cfg.get_emulators_info().at("Custom"); + if (!address.empty()) { + remote_info.adb.addresses.push_back(address); + } + + ret = Ctrler.try_capture(remote_info, true); + + m_inited = ret; + return ret; +} + +bool asst::Assistant::catch_fake() +{ + LogTraceFunction; + + stop(); + + m_inited = true; + return true; +} + +bool asst::Assistant::append_start_up(bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + auto task_ptr = std::make_shared(task_callback, (void*)this); + task_ptr->set_task_chain("StartUp"); + task_ptr->set_tasks({ "StartUp" }); + task_ptr->set_times_limit("ReturnToTerminal", 0); + task_ptr->set_times_limit("Terminal", 0); + + m_tasks_queue.emplace(task_ptr); + + if (!only_append) { + return start(false); + } + + return true; +} + +bool asst::Assistant::append_fight(const std::string& stage, int mecidine, int stone, int times, bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + constexpr const char* TaskChain = "Fight"; + + // 进入选关界面(主界面的“终端”点进去) + auto terminal_task_ptr = std::make_shared(task_callback, (void*)this); + terminal_task_ptr->set_task_chain(TaskChain); + terminal_task_ptr->set_tasks({ "StageBegin" }); + terminal_task_ptr->set_times_limit("LastBattle", 0); + terminal_task_ptr->set_times_limit("StartButton1", 0); + terminal_task_ptr->set_times_limit("StartButton2", 0); + terminal_task_ptr->set_times_limit("MedicineConfirm", 0); + terminal_task_ptr->set_times_limit("StoneConfirm", 0); + + // 进入对应的关卡 + auto stage_task_ptr = std::make_shared(task_callback, (void*)this); + stage_task_ptr->set_task_chain(TaskChain); + stage_task_ptr->set_tasks({ stage }); + stage_task_ptr->set_times_limit("StartButton1", 0); + stage_task_ptr->set_times_limit("StartButton2", 0); + stage_task_ptr->set_times_limit("MedicineConfirm", 0); + stage_task_ptr->set_times_limit("StoneConfirm", 0); + + // 开始战斗任务 + auto fight_task_ptr = std::make_shared(task_callback, (void*)this); + fight_task_ptr->set_task_chain(TaskChain); + fight_task_ptr->set_tasks({ "FightBegin" }); + fight_task_ptr->set_times_limit("MedicineConfirm", mecidine); + fight_task_ptr->set_times_limit("StoneConfirm", stone); + fight_task_ptr->set_times_limit("StartButton1", times); + fight_task_ptr->set_times_limit("StartButton2", times); + + std::unique_lock lock(m_mutex); + + if (!stage.empty()) { + m_tasks_queue.emplace(terminal_task_ptr); + m_tasks_queue.emplace(stage_task_ptr); + } + m_tasks_queue.emplace(fight_task_ptr); + + if (!only_append) { + return start(false); + } + + return true; +} + +bool asst::Assistant::append_award(bool only_append) +{ + return append_process_task("AwardBegin", "Award"); +} + +bool asst::Assistant::append_visit(bool only_append) +{ + return append_process_task("VisitBegin", "Visit"); +} + +bool asst::Assistant::append_mall(bool with_shopping, bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + const std::string task_chain = "Mall"; + + append_process_task("MallBegin", task_chain); + + if (with_shopping) { + auto shopping_task_ptr = std::make_shared(task_callback, (void*)this); + shopping_task_ptr->set_task_chain(task_chain); + m_tasks_queue.emplace(shopping_task_ptr); + } + + if (!only_append) { + return start(false); + } + + return true; +} + +bool Assistant::append_process_task(const std::string& task_name, std::string task_chain, int retry_times) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + //std::unique_lock lock(m_mutex); + + if (task_chain.empty()) { + task_chain = task_name; + } + + auto task_ptr = std::make_shared(task_callback, (void*)this); + task_ptr->set_task_chain(task_chain); + task_ptr->set_tasks({ task_name }); + task_ptr->set_retry_times(retry_times); + + m_tasks_queue.emplace(task_ptr); + + //if (!only_append) { + // return start(false); + //} + + return true; +} + +bool asst::Assistant::append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + static const std::string TaskChain = "Recruit"; + + append_process_task("RecruitBegin", TaskChain); + + auto recruit_task_ptr = std::make_shared(task_callback, (void*)this); + recruit_task_ptr->set_max_times(max_times); + recruit_task_ptr->set_need_refresh(need_refresh); + recruit_task_ptr->set_select_level(select_level); + recruit_task_ptr->set_confirm_level(confirm_level); + recruit_task_ptr->set_task_chain(TaskChain); + recruit_task_ptr->set_retry_times(AutoRecruitTaskRetryTimesDefault); + + m_tasks_queue.emplace(recruit_task_ptr); + + return true; +} + +#ifdef LOG_TRACE +bool Assistant::append_debug() +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + { + constexpr static const char* DebugTaskChain = "Debug"; + auto shift_task_ptr = std::make_shared(task_callback, (void*)this); + shift_task_ptr->set_work_mode(infrast::WorkMode::Aggressive); + shift_task_ptr->set_facility("Control"); + shift_task_ptr->set_product("MoodAddition"); + shift_task_ptr->set_task_chain(DebugTaskChain); + m_tasks_queue.emplace(shift_task_ptr); + } + + return true; +} +#endif + +bool Assistant::start_recruit_calc(const std::vector& select_level, bool set_time) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + std::unique_lock lock(m_mutex); + + auto task_ptr = std::make_shared(task_callback, (void*)this); + task_ptr->set_param(select_level, set_time); + task_ptr->set_retry_times(OpenRecruitTaskRetryTimesDefault); + task_ptr->set_task_chain("RecruitCalc"); + m_tasks_queue.emplace(task_ptr); + + return start(false); +} + +bool asst::Assistant::append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append) +{ + LogTraceFunction; + if (!m_inited) { + return false; + } + + // 保留接口,目前强制按激进模式进行换班 + work_mode = infrast::WorkMode::Aggressive; + + constexpr static const char* InfrastTaskCahin = "Infrast"; + + // 这个流程任务,结束的时候是处于基建主界面的。既可以用于进入基建,也可以用于从设施里返回基建主界面 + + auto append_infrast_begin = [&]() { + append_process_task("InfrastBegin", InfrastTaskCahin); + }; + + append_infrast_begin(); + + auto info_task_ptr = std::make_shared(task_callback, (void*)this); + info_task_ptr->set_work_mode(work_mode); + info_task_ptr->set_task_chain(InfrastTaskCahin); + info_task_ptr->set_mood_threshold(dorm_threshold); + + m_tasks_queue.emplace(info_task_ptr); + + // 因为后期要考虑多任务间的联动等,所以这些任务的声明暂时不放到for循环中 + auto mfg_task_ptr = std::make_shared(task_callback, (void*)this); + mfg_task_ptr->set_work_mode(work_mode); + mfg_task_ptr->set_task_chain(InfrastTaskCahin); + mfg_task_ptr->set_mood_threshold(dorm_threshold); + mfg_task_ptr->set_uses_of_drone(uses_of_drones); + auto trade_task_ptr = std::make_shared(task_callback, (void*)this); + trade_task_ptr->set_work_mode(work_mode); + trade_task_ptr->set_task_chain(InfrastTaskCahin); + trade_task_ptr->set_mood_threshold(dorm_threshold); + trade_task_ptr->set_uses_of_drone(uses_of_drones); + auto power_task_ptr = std::make_shared(task_callback, (void*)this); + power_task_ptr->set_work_mode(work_mode); + power_task_ptr->set_task_chain(InfrastTaskCahin); + power_task_ptr->set_mood_threshold(dorm_threshold); + auto office_task_ptr = std::make_shared(task_callback, (void*)this); + office_task_ptr->set_work_mode(work_mode); + office_task_ptr->set_task_chain(InfrastTaskCahin); + office_task_ptr->set_mood_threshold(dorm_threshold); + auto recpt_task_ptr = std::make_shared(task_callback, (void*)this); + recpt_task_ptr->set_work_mode(work_mode); + recpt_task_ptr->set_task_chain(InfrastTaskCahin); + recpt_task_ptr->set_mood_threshold(dorm_threshold); + auto control_task_ptr = std::make_shared(task_callback, (void*)this); + control_task_ptr->set_work_mode(work_mode); + control_task_ptr->set_task_chain(InfrastTaskCahin); + control_task_ptr->set_mood_threshold(dorm_threshold); + + auto dorm_task_ptr = std::make_shared(task_callback, (void*)this); + dorm_task_ptr->set_work_mode(work_mode); + dorm_task_ptr->set_task_chain(InfrastTaskCahin); + dorm_task_ptr->set_mood_threshold(dorm_threshold); + + for (const auto& facility : order) { + if (facility == "Dorm") { + m_tasks_queue.emplace(dorm_task_ptr); + } + else if (facility == "Mfg") { + m_tasks_queue.emplace(mfg_task_ptr); + } + else if (facility == "Trade") { + m_tasks_queue.emplace(trade_task_ptr); + } + else if (facility == "Power") { + m_tasks_queue.emplace(power_task_ptr); + } + else if (facility == "Office") { + m_tasks_queue.emplace(office_task_ptr); + } + else if (facility == "Reception") { + m_tasks_queue.emplace(recpt_task_ptr); + } + else if (facility == "Control") { + m_tasks_queue.emplace(control_task_ptr); + } + else { + Log.error("append_infrast | Unknown facility", facility); + } + append_infrast_begin(); + } + + if (!only_append) { + return start(false); + } + + return true; +} + +void asst::Assistant::set_penguin_id(const std::string& id) +{ + auto& opt = Resrc.cfg().get_options(); + if (id.empty()) { + opt.penguin_report.extra_param.clear(); + } + else { + opt.penguin_report.extra_param = "-H \"authorization: PenguinID " + id + "\""; + } +} + +bool asst::Assistant::start(bool block) +{ + LogTraceFunction; + Log.trace("Start |", block ? "block" : "non block"); + + if (!m_thread_idle || !m_inited) { + return false; + } + std::unique_lock lock; + if (block) { // 外部调用 + lock = std::unique_lock(m_mutex); + } + + m_thread_idle = false; + m_condvar.notify_one(); + + return true; +} + +bool Assistant::stop(bool block) +{ + LogTraceFunction; + Log.trace("Stop |", block ? "block" : "non block"); + + m_thread_idle = true; + + std::unique_lock lock; + if (block) { // 外部调用 + lock = std::unique_lock(m_mutex); + } + decltype(m_tasks_queue) empty; + m_tasks_queue.swap(empty); + + clear_cache(); + + return true; +} + +void Assistant::working_proc() +{ + LogTraceFunction; + + std::string pre_taskchain; + while (!m_thread_exit) { + //LogTraceScope("Assistant::working_proc Loop"); + std::unique_lock lock(m_mutex); + + if (!m_thread_idle && !m_tasks_queue.empty()) { + auto start_time = std::chrono::steady_clock::now(); + + auto task_ptr = m_tasks_queue.front(); + + std::string cur_taskchain = task_ptr->get_task_chain(); + json::value task_json = json::object{ + {"task_chain", cur_taskchain}, + }; + + if (cur_taskchain != pre_taskchain) { + task_callback(AsstMsg::TaskChainStart, task_json, this); + pre_taskchain = cur_taskchain; + } + + task_ptr->set_exit_flag(&m_thread_idle); + bool ret = task_ptr->run(); + m_tasks_queue.pop(); + + if (!ret) { + task_callback(AsstMsg::TaskError, task_json, this); + } + else if (m_tasks_queue.empty() || cur_taskchain != m_tasks_queue.front()->get_task_chain()) { + task_callback(AsstMsg::TaskChainCompleted, task_json, this); + } + if (m_tasks_queue.empty()) { + task_callback(AsstMsg::AllTasksCompleted, task_json, this); + } + + //clear_cache(); + + auto& delay = Resrc.cfg().get_options().task_delay; + m_condvar.wait_until(lock, start_time + std::chrono::milliseconds(delay), + [&]() -> bool { return m_thread_idle; }); + } + else { + pre_taskchain.clear(); + m_thread_idle = true; + Log.flush(); + //controller.set_idle(true); + m_condvar.wait(lock); + } + } +} + +void Assistant::msg_proc() +{ + LogTraceFunction; + + while (!m_thread_exit) { + //LogTraceScope("Assistant::msg_proc Loop"); + std::unique_lock lock(m_msg_mutex); + if (!m_msg_queue.empty()) { + // 结构化绑定只能是引用,后续的pop会使引用失效,所以需要重新构造一份,这里采用了move的方式 + auto&& [temp_msg, temp_detail] = m_msg_queue.front(); + AsstMsg msg = std::move(temp_msg); + json::value detail = std::move(temp_detail); + m_msg_queue.pop(); + lock.unlock(); + + if (m_callback) { + m_callback(msg, detail, m_callback_arg); + } + } + else { + m_msg_condvar.wait(lock); + } + } +} + +void Assistant::task_callback(AsstMsg msg, const json::value& detail, void* custom_arg) +{ + Log.trace("Assistant::task_callback |", msg, detail.to_string()); + + Assistant* p_this = (Assistant*)custom_arg; + json::value more_detail = detail; + switch (msg) { + case AsstMsg::PtrIsNull: + case AsstMsg::ImageIsEmpty: + p_this->stop(false); + break; + case AsstMsg::StageDrops: + more_detail = p_this->organize_stage_drop(more_detail); + break; + default: + break; + } + + // Todo: 有些不需要回调给外部的消息,得在这里给拦截掉 + // 加入回调消息队列,由回调消息线程外抛给外部 + p_this->append_callback(msg, std::move(more_detail)); +} + +void asst::Assistant::append_callback(AsstMsg msg, json::value detail) +{ + std::unique_lock lock(m_msg_mutex); + m_msg_queue.emplace(msg, std::move(detail)); + m_msg_condvar.notify_one(); +} + +void Assistant::clear_cache() +{ + Status.clear(); + Resrc.item().clear_drop_count(); + //Task.clear_cache(); +} + +json::value asst::Assistant::organize_stage_drop(const json::value& rec) +{ + json::value dst = rec; + auto& item = Resrc.item(); + for (json::value& drop : dst["drops"].as_array()) { + std::string id = drop["itemId"].as_string(); + int quantity = drop["quantity"].as_integer(); + item.increase_drop_count(id, quantity); + const std::string& name = item.get_item_name(id); + drop["itemName"] = name.empty() ? "未知材料" : name; + } + std::vector statistics_vec; + for (auto&& [id, count] : item.get_drop_count()) { + json::value info; + info["itemId"] = id; + const std::string& name = item.get_item_name(id); + info["itemName"] = name.empty() ? "未知材料" : name; + info["count"] = count; + statistics_vec.emplace_back(std::move(info)); + } + //// 排个序,数量多的放前面 + //std::sort(statistics_vec.begin(), statistics_vec.end(), + // [](const json::value& lhs, const json::value& rhs) -> bool { + // return lhs.at("count").as_integer() > rhs.at("count").as_integer(); + // }); + + dst["statistics"] = json::array(std::move(statistics_vec)); + + Log.trace("organize_stage_drop | ", dst.to_string()); + + return dst; +} diff --git a/src/MeoAssistant/Assistant.h b/src/MeoAssistant/Assistant.h index 7fb3cbacf1..0305bb9d9a 100644 --- a/src/MeoAssistant/Assistant.h +++ b/src/MeoAssistant/Assistant.h @@ -1,111 +1,111 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "AbstractTask.h" -#include "AsstDef.h" -#include "AsstMsg.h" -#include "AsstInfrastDef.h" - -namespace cv -{ - class Mat; -} - -namespace asst -{ - class Controller; - class Identify; - - class Assistant - { - public: - Assistant(std::string dirname, AsstCallback callback = nullptr, void* callback_arg = nullptr); - ~Assistant(); - - // 根据配置文件,决定捕获模拟器、USB 还是远程设备 - bool catch_default(); - // 捕获模拟器 - bool catch_emulator(const std::string& emulator_name = std::string()); - // 捕获自定义设备 - bool catch_custom(const std::string& address = std::string()); - // 不实际进行捕获,调试用接口 - bool catch_fake(); - - // 添加开始游戏的任务(进入到主界面) - bool append_start_up(bool only_append = true); - // 添加刷理智任务 - bool append_fight(const std::string& stage, int mecidine = 0, int stone = 0, int times = INT_MAX, bool only_append = true); - // 添加领取日常任务奖励任务 - bool append_award(bool only_append = true); - // 添加访问好友任务 - bool append_visit(bool only_append = true); - // 添加领取当日信用及信用购的任务 - bool append_mall(bool with_shopping, bool only_append = true); - - // 添加基建换班任务任务 - bool append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append = true); - - // 添加自动公招任务 - // 参数max_times: 最多进行几次公招 - // 参数select_level: 需要的选择Tags的等级 - // 参数confirm_level: 需要点击确认按钮的等级 - bool append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh); - -#ifdef LOG_TRACE - // 调试用 - bool append_debug(); -#endif - - // 开始公开招募计算 - bool start_recruit_calc(const std::vector& select_level, bool set_time); - - // 开始执行任务队列 - bool start(bool block = true); - // 停止任务队列并清空 - bool stop(bool block = true); - - // 设置企鹅数据汇报个人ID - void set_penguin_id(const std::string& id); - - [[deprecated]] bool set_param(const std::string& type, const std::string& param, const std::string& value); - - static constexpr int ProcessTaskRetryTimesDefault = 20; - static constexpr int OpenRecruitTaskRetryTimesDefault = 10; - static constexpr int AutoRecruitTaskRetryTimesDefault = 5; - - private: - void working_proc(); - void msg_proc(); - static void task_callback(AsstMsg msg, const json::value& detail, void* custom_arg); - - bool append_process_task(const std::string& task_name, std::string task_chain = std::string(), int retry_times = ProcessTaskRetryTimesDefault); - void append_callback(AsstMsg msg, json::value detail); - void clear_cache(); - json::value organize_stage_drop(const json::value& rec); // 整理关卡掉落的材料信息 - - bool m_inited = false; - std::string m_dirname; - - bool m_thread_exit = false; - std::queue> m_tasks_queue; - AsstCallback m_callback = nullptr; - void* m_callback_arg = nullptr; - - bool m_thread_idle = true; - std::thread m_working_thread; - std::mutex m_mutex; - std::condition_variable m_condvar; - - std::thread m_msg_thread; - std::queue> m_msg_queue; - std::mutex m_msg_mutex; - std::condition_variable m_msg_condvar; - }; -} +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "AbstractTask.h" +#include "AsstDef.h" +#include "AsstMsg.h" +#include "AsstInfrastDef.h" + +namespace cv +{ + class Mat; +} + +namespace asst +{ + class Controller; + class Identify; + + class Assistant + { + public: + Assistant(std::string dirname, AsstCallback callback = nullptr, void* callback_arg = nullptr); + ~Assistant(); + + // 根据配置文件,决定捕获模拟器、USB 还是远程设备 + bool catch_default(); + // 捕获模拟器 + bool catch_emulator(const std::string& emulator_name = std::string()); + // 捕获自定义设备 + bool catch_custom(const std::string& address = std::string()); + // 不实际进行捕获,调试用接口 + bool catch_fake(); + + // 添加开始游戏的任务(进入到主界面) + bool append_start_up(bool only_append = true); + // 添加刷理智任务 + bool append_fight(const std::string& stage, int mecidine = 0, int stone = 0, int times = INT_MAX, bool only_append = true); + // 添加领取日常任务奖励任务 + bool append_award(bool only_append = true); + // 添加访问好友任务 + bool append_visit(bool only_append = true); + // 添加领取当日信用及信用购的任务 + bool append_mall(bool with_shopping, bool only_append = true); + + // 添加基建换班任务任务 + bool append_infrast(infrast::WorkMode work_mode, const std::vector& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append = true); + + // 添加自动公招任务 + // 参数max_times: 最多进行几次公招 + // 参数select_level: 需要的选择Tags的等级 + // 参数confirm_level: 需要点击确认按钮的等级 + bool append_recruit(unsigned max_times, const std::vector& select_level, const std::vector& confirm_level, bool need_refresh); + +#ifdef LOG_TRACE + // 调试用 + bool append_debug(); +#endif + + // 开始公开招募计算 + bool start_recruit_calc(const std::vector& select_level, bool set_time); + + // 开始执行任务队列 + bool start(bool block = true); + // 停止任务队列并清空 + bool stop(bool block = true); + + // 设置企鹅数据汇报个人ID + void set_penguin_id(const std::string& id); + + [[deprecated]] bool set_param(const std::string& type, const std::string& param, const std::string& value); + + static constexpr int ProcessTaskRetryTimesDefault = 20; + static constexpr int OpenRecruitTaskRetryTimesDefault = 10; + static constexpr int AutoRecruitTaskRetryTimesDefault = 5; + + private: + void working_proc(); + void msg_proc(); + static void task_callback(AsstMsg msg, const json::value& detail, void* custom_arg); + + bool append_process_task(const std::string& task_name, std::string task_chain = std::string(), int retry_times = ProcessTaskRetryTimesDefault); + void append_callback(AsstMsg msg, json::value detail); + void clear_cache(); + json::value organize_stage_drop(const json::value& rec); // 整理关卡掉落的材料信息 + + bool m_inited = false; + std::string m_dirname; + + bool m_thread_exit = false; + std::queue> m_tasks_queue; + AsstCallback m_callback = nullptr; + void* m_callback_arg = nullptr; + + bool m_thread_idle = true; + std::thread m_working_thread; + std::mutex m_mutex; + std::condition_variable m_condvar; + + std::thread m_msg_thread; + std::queue> m_msg_queue; + std::mutex m_msg_mutex; + std::condition_variable m_msg_condvar; + }; +} diff --git a/src/MeoAssistant/AsstCaller.cpp b/src/MeoAssistant/AsstCaller.cpp index e2b56e9fad..60c6259d4a 100644 --- a/src/MeoAssistant/AsstCaller.cpp +++ b/src/MeoAssistant/AsstCaller.cpp @@ -1,264 +1,264 @@ -#include "AsstCaller.h" - -#include -#include - -#include - -#include "Assistant.h" -#include "AsstDef.h" -#include "AsstUtils.hpp" -#include "Version.h" - -#if 0 -#if _MSC_VER -// Win32平台下Dll的入口 -BOOL APIENTRY DllMain(HINSTANCE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved -) -{ - UNREFERENCED_PARAMETER(hModule); - UNREFERENCED_PARAMETER(lpReserved); - switch (ul_reason_for_call) { - case DLL_PROCESS_ATTACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; - } - return TRUE; -} -#elif VA_GNUC - -#endif -#endif - -AsstCallback _callback = nullptr; - -void CallbackTrans(asst::AsstMsg msg, const json::value& json, void* custom_arg) -{ - _callback(static_cast(msg), asst::utils::utf8_to_gbk(json.to_string()).c_str(), custom_arg); -} - -asst::Assistant* AsstCreate(const char* dirname) -{ - try { - return new asst::Assistant(dirname); - } - catch (std::exception& e) { - std::cerr << "create failed: " << e.what() << std::endl; - } - catch (...) { - std::cerr << "create failed: unknown exception" << std::endl; - } - return nullptr; -} - -asst::Assistant* AsstCreateEx(const char* dirname, AsstCallback callback, void* custom_arg) -{ - try { - // 创建多实例回调会有问题,有空再慢慢整 - _callback = callback; - return new asst::Assistant(dirname, CallbackTrans, custom_arg); - } - catch (...) { - return nullptr; - } -} - -void AsstDestroy(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return; - } - - delete p_asst; - p_asst = nullptr; -} - -bool AsstCatchDefault(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return ((asst::Assistant*)p_asst)->catch_default(); -} - -bool AsstCatchEmulator(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return ((asst::Assistant*)p_asst)->catch_emulator(); -} - -bool AsstCatchCustom(asst::Assistant* p_asst, const char* address) -{ - if (p_asst == nullptr) { - return false; - } - - return ((asst::Assistant*)p_asst)->catch_custom(address); -} - -bool AsstCatchFake(asst::Assistant* p_asst) -{ -#ifdef LOG_TRACE - if (p_asst == nullptr) { - return false; - } - - return p_asst->catch_fake(); -#else - return false; -#endif // LOG_TRACE -} - -bool ASSTAPI AsstAppendStartUp(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_start_up(); -} - -bool AsstAppendFight(asst::Assistant* p_asst, const char* stage, int max_mecidine, int max_stone, int max_times) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_fight(stage, max_mecidine, max_stone, max_times); -} - -bool AsstAppendAward(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_award(); -} - -bool AsstAppendVisit(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_visit(); -} - -bool AsstAppendMall(asst::Assistant* p_asst, bool with_shopping) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->append_mall(with_shopping); -} - -//bool AsstAppendProcessTask(asst::Assistant* p_asst, const char* task_name) -//{ -// if (p_asst == nullptr) { -// return false; -// } -// -// return p_asst->append_process_task(task_name); -//} - -bool AsstStartRecruitCalc(asst::Assistant* p_asst, const int select_level[], int required_len, bool set_time) -{ - if (p_asst == nullptr) { - return false; - } - std::vector level_vector; - level_vector.assign(select_level, select_level + required_len); - return p_asst->start_recruit_calc(level_vector, set_time); -} - -bool AsstAppendInfrast(asst::Assistant* p_asst, int work_mode, const char** order, int order_size, const char* uses_of_drones, double dorm_threshold) -{ - if (p_asst == nullptr) { - return false; - } - std::vector order_vector; - order_vector.assign(order, order + order_size); - - return p_asst->append_infrast( - static_cast(work_mode), - order_vector, - uses_of_drones, - dorm_threshold); -} - -bool AsstAppendRecruit(asst::Assistant* p_asst, int max_times, const int select_level[], int select_len, const int confirm_level[], int confirm_len, bool need_refresh) -{ - if (p_asst == nullptr) { - return false; - } - std::vector required_vector; - required_vector.assign(select_level, select_level + select_len); - std::vector confirm_vector; - confirm_vector.assign(confirm_level, confirm_level + confirm_len); - - return p_asst->append_recruit(max_times, required_vector, confirm_vector, need_refresh); -} - -bool AsstStart(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->start(); -} - -bool AsstStop(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } - - return p_asst->stop(); -} - -bool AsstSetPenguinId(asst::Assistant* p_asst, const char* id) -{ - if (p_asst == nullptr) { - return false; - } - - p_asst->set_penguin_id(id); - return true; -} - -//bool AsstSetParam(asst::Assistant* p_asst, const char* type, const char* param, const char* value) -//{ -// if (p_asst == nullptr) { -// return false; -// } -// -// return p_asst->set_param(type, param, value); -//} - -const char* AsstGetVersion() -{ - return asst::Version; -} - -bool AsstAppendDebug(asst::Assistant* p_asst) -{ - if (p_asst == nullptr) { - return false; - } -#if LOG_TRACE - return p_asst->append_debug(); -#else - return false; -#endif // LOG_TRACE -} +#include "AsstCaller.h" + +#include +#include + +#include + +#include "Assistant.h" +#include "AsstDef.h" +#include "AsstUtils.hpp" +#include "Version.h" + +#if 0 +#if _MSC_VER +// Win32平台下Dll的入口 +BOOL APIENTRY DllMain(HINSTANCE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved +) +{ + UNREFERENCED_PARAMETER(hModule); + UNREFERENCED_PARAMETER(lpReserved); + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#elif VA_GNUC + +#endif +#endif + +AsstCallback _callback = nullptr; + +void CallbackTrans(asst::AsstMsg msg, const json::value& json, void* custom_arg) +{ + _callback(static_cast(msg), asst::utils::utf8_to_gbk(json.to_string()).c_str(), custom_arg); +} + +asst::Assistant* AsstCreate(const char* dirname) +{ + try { + return new asst::Assistant(dirname); + } + catch (std::exception& e) { + std::cerr << "create failed: " << e.what() << std::endl; + } + catch (...) { + std::cerr << "create failed: unknown exception" << std::endl; + } + return nullptr; +} + +asst::Assistant* AsstCreateEx(const char* dirname, AsstCallback callback, void* custom_arg) +{ + try { + // 创建多实例回调会有问题,有空再慢慢整 + _callback = callback; + return new asst::Assistant(dirname, CallbackTrans, custom_arg); + } + catch (...) { + return nullptr; + } +} + +void AsstDestroy(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return; + } + + delete p_asst; + p_asst = nullptr; +} + +bool AsstCatchDefault(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return ((asst::Assistant*)p_asst)->catch_default(); +} + +bool AsstCatchEmulator(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return ((asst::Assistant*)p_asst)->catch_emulator(); +} + +bool AsstCatchCustom(asst::Assistant* p_asst, const char* address) +{ + if (p_asst == nullptr) { + return false; + } + + return ((asst::Assistant*)p_asst)->catch_custom(address); +} + +bool AsstCatchFake(asst::Assistant* p_asst) +{ +#ifdef LOG_TRACE + if (p_asst == nullptr) { + return false; + } + + return p_asst->catch_fake(); +#else + return false; +#endif // LOG_TRACE +} + +bool ASSTAPI AsstAppendStartUp(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_start_up(); +} + +bool AsstAppendFight(asst::Assistant* p_asst, const char* stage, int max_mecidine, int max_stone, int max_times) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_fight(stage, max_mecidine, max_stone, max_times); +} + +bool AsstAppendAward(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_award(); +} + +bool AsstAppendVisit(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_visit(); +} + +bool AsstAppendMall(asst::Assistant* p_asst, bool with_shopping) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->append_mall(with_shopping); +} + +//bool AsstAppendProcessTask(asst::Assistant* p_asst, const char* task_name) +//{ +// if (p_asst == nullptr) { +// return false; +// } +// +// return p_asst->append_process_task(task_name); +//} + +bool AsstStartRecruitCalc(asst::Assistant* p_asst, const int select_level[], int required_len, bool set_time) +{ + if (p_asst == nullptr) { + return false; + } + std::vector level_vector; + level_vector.assign(select_level, select_level + required_len); + return p_asst->start_recruit_calc(level_vector, set_time); +} + +bool AsstAppendInfrast(asst::Assistant* p_asst, int work_mode, const char** order, int order_size, const char* uses_of_drones, double dorm_threshold) +{ + if (p_asst == nullptr) { + return false; + } + std::vector order_vector; + order_vector.assign(order, order + order_size); + + return p_asst->append_infrast( + static_cast(work_mode), + order_vector, + uses_of_drones, + dorm_threshold); +} + +bool AsstAppendRecruit(asst::Assistant* p_asst, int max_times, const int select_level[], int select_len, const int confirm_level[], int confirm_len, bool need_refresh) +{ + if (p_asst == nullptr) { + return false; + } + std::vector required_vector; + required_vector.assign(select_level, select_level + select_len); + std::vector confirm_vector; + confirm_vector.assign(confirm_level, confirm_level + confirm_len); + + return p_asst->append_recruit(max_times, required_vector, confirm_vector, need_refresh); +} + +bool AsstStart(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->start(); +} + +bool AsstStop(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } + + return p_asst->stop(); +} + +bool AsstSetPenguinId(asst::Assistant* p_asst, const char* id) +{ + if (p_asst == nullptr) { + return false; + } + + p_asst->set_penguin_id(id); + return true; +} + +//bool AsstSetParam(asst::Assistant* p_asst, const char* type, const char* param, const char* value) +//{ +// if (p_asst == nullptr) { +// return false; +// } +// +// return p_asst->set_param(type, param, value); +//} + +const char* AsstGetVersion() +{ + return asst::Version; +} + +bool AsstAppendDebug(asst::Assistant* p_asst) +{ + if (p_asst == nullptr) { + return false; + } +#if LOG_TRACE + return p_asst->append_debug(); +#else + return false; +#endif // LOG_TRACE +} diff --git a/src/MeoAssistant/AsstDef.h b/src/MeoAssistant/AsstDef.h index 7250157629..bc24a8ad58 100644 --- a/src/MeoAssistant/AsstDef.h +++ b/src/MeoAssistant/AsstDef.h @@ -1,258 +1,259 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace json -{ - class value; -} - -namespace asst -{ - constexpr double DoubleDiff = 1e-12; - - constexpr static int WindowWidthDefault = 1280; - constexpr static int WindowHeightDefault = 720; - - constexpr static double TemplThresholdDefault = 0.8; - - struct Point - { - Point() = default; - Point(const Point&) noexcept = default; - Point(Point&&) noexcept = default; - Point(int x, int y) : x(x), y(y) {} - Point& operator=(const Point&) noexcept = default; - Point& operator=(Point&&) noexcept = default; - int x = 0; - int y = 0; - }; - - struct Rect - { - Rect() = default; - Rect(const Rect&) noexcept = default; - Rect(Rect&&) noexcept = default; - Rect(int x, int y, int width, int height) - : x(x), y(y), width(width), height(height) - {} - Rect operator*(double rhs) const - { - return { x, y, static_cast(width * rhs), static_cast(height * rhs) }; - } - int area() const noexcept - { - return width * height; - } - Rect center_zoom(double scale, int max_width = INT_MAX, int max_height = INT_MAX) const - { - int half_width_scale = static_cast(width * (1 - scale) / 2); - int half_hight_scale = static_cast(height * (1 - scale) / 2); - Rect dst(x + half_width_scale, y + half_hight_scale, - static_cast(width * scale), static_cast(height * scale)); - if (dst.x < 0) { - dst.x = 0; - } - if (dst.y < 0) { - dst.y = 0; - } - if (dst.width + dst.x >= max_width) { - dst.width = max_width - dst.x; - } - if (dst.height + dst.y >= max_height) { - dst.height = max_height - dst.y; - } - return dst; - } - Rect& operator=(const Rect&) noexcept = default; - Rect& operator=(Rect&&) noexcept = default; - bool empty() const noexcept { return width == 0 || height == 0; } - bool operator==(const Rect& rhs) const noexcept - { - return x == rhs.x && y == rhs.y && width == rhs.width && height == rhs.height; - } - bool include(const Rect& rhs) const noexcept - { - return x <= rhs.x && y <= rhs.y && (x + width) >= (rhs.x + rhs.width) && (y + height) >= (rhs.y + rhs.height); - } - std::string to_string() const - { - return "[ " + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(width) + ", " + std::to_string(height) + " ]"; - } - - int x = 0; - int y = 0; - int width = 0; - int height = 0; - }; - - struct TextRect - { - TextRect() = default; - TextRect(const TextRect&) = default; - TextRect(TextRect&&) noexcept = default; - - explicit operator std::string() const noexcept { return text; } - explicit operator Rect() const noexcept { return rect; } - std::string to_string() const - { - return text + " : " + rect.to_string() + ", score: " + std::to_string(score); - } - TextRect& operator=(const TextRect&) = default; - TextRect& operator=(TextRect&&) noexcept = default; - bool operator==(const TextRect& rhs) const noexcept - { - return text == rhs.text && rect == rhs.rect; - } - - std::string text; - Rect rect; - float score; - }; - using TextRectProc = std::function; - - enum class AlgorithmType - { - Invaild = -1, - JustReturn, - MatchTemplate, - CompareHist, - OcrDetect - }; - - struct MatchRect - { - MatchRect() = default; - MatchRect(const MatchRect&) = default; - MatchRect(MatchRect&&) noexcept = default; - - explicit operator Rect() const noexcept { return rect; } - MatchRect& operator=(const MatchRect&) = default; - MatchRect& operator=(MatchRect&&) noexcept = default; - - AlgorithmType algorithm = AlgorithmType::Invaild; - double score = 0.0; - Rect rect; - }; -} - -namespace std -{ - template <> - class hash - { - public: - size_t operator()(const asst::Rect& rect) const - { - return std::hash()(rect.x) ^ std::hash()(rect.y) ^ std::hash()(rect.width) ^ std::hash()(rect.height); - } - }; - template <> - class hash - { - public: - size_t operator()(const asst::TextRect& tr) const - { - return std::hash()(tr.text) ^ std::hash()(tr.rect); - } - }; -} - -namespace asst -{ - enum class ProcessTaskAction - { - Invalid = 0, - BasicClick = 0x100, - ClickSelf = BasicClick | 1, // 点击自身位置 - ClickRect = BasicClick | 2, // 点击指定区域 - ClickRand = BasicClick | 4, // 点击随机区域 - DoNothing = 0x200, // 什么都不做 - Stop = 0x400, // 停止当前Task - StageDrops = 0x800, // 关卡结束,特化动作 - BasicSwipe = 0x1000, - SwipeToTheLeft = BasicSwipe | 1, // 往左划一下 - SwipeToTheRight = BasicSwipe | 2, // 往右划一下 - }; - - // 任务信息 - struct TaskInfo - { - virtual ~TaskInfo() = default; - std::string name; // 任务名 - AlgorithmType algorithm = // 图像算法类型 - AlgorithmType::Invaild; - ProcessTaskAction action = // 要进行的操作 - ProcessTaskAction::Invalid; - std::vector next; // 下一个可能的任务(列表) - int max_times = INT_MAX; // 任务最多执行多少次 - std::vector exceeded_next; // 达到最多次数了之后,下一个可能的任务(列表) - std::vector reduce_other_times; // 执行了该任务后,需要减少别的任务的执行次数。例如执行了吃理智药,则说明上一次点击蓝色开始行动按钮没生效,所以蓝色开始行动要-1 - asst::Rect specific_rect; // 指定区域,目前仅针对ClickRect任务有用,会点这个区域 - int pre_delay = 0; // 执行该任务前的延时 - int rear_delay = 0; // 执行该任务后的延时 - int retry_times = INT_MAX; // 未找到图像时的重试次数 - Rect roi; // 要识别的区域,若为0则全图识别 - Rect rect_move; // 识别结果移动:有些结果识别到的,和要点击的不是同一个位置。即识别到了res,点击res + result_move的位置 - bool cache = false; // 是否使用缓存区域 - Rect region_of_appeared; // 缓存区域:上次识别成功过,本次只在这个rect里识别,省点性能 - }; - - // 文字识别任务的信息 - struct OcrTaskInfo : public TaskInfo - { - virtual ~OcrTaskInfo() = default; - std::vector text; // 文字的容器,匹配到这里面任一个,就算匹配上了 - bool need_full_match = false; // 是否需要全匹配,否则搜索到子串就算匹配上了 - std::unordered_map - replace_map; // 部分文字容易识别错,字符串强制replace之后,再进行匹配 - }; - - // 图片匹配任务的信息 - struct MatchTaskInfo : public TaskInfo - { - virtual ~MatchTaskInfo() = default; - std::string templ_name; // 匹配模板图片文件名 - double templ_threshold = 0; // 模板匹配阈值 - double special_threshold = 0; // 某些任务使用的特殊的阈值 - std::pair mask_range; // 掩码的二值化范围 - }; - - struct HandleInfo - { - std::string class_name; - std::string window_name; - }; - - struct AdbCmd - { - std::string path; - std::vector addresses; // 会优先尝试连接addresses中的地址,若均失败,则会使用devices获取地址 - std::string devices; - std::string address_regex; - std::string connect; - std::string click; - std::string swipe; - std::string display; - std::string display_format; - std::string screencap; - std::string release; - //std::string pullscreen; - int display_width = 0; - int display_height = 0; - }; - - struct EmulatorInfo - { - std::string name; - HandleInfo handle; - AdbCmd adb; - std::string path; - }; -} +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace json +{ + class value; +} + +namespace asst +{ + constexpr double DoubleDiff = 1e-12; + + constexpr static int WindowWidthDefault = 1280; + constexpr static int WindowHeightDefault = 720; + + constexpr static double TemplThresholdDefault = 0.8; + + struct Point + { + Point() = default; + Point(const Point&) noexcept = default; + Point(Point&&) noexcept = default; + Point(int x, int y) : x(x), y(y) {} + Point& operator=(const Point&) noexcept = default; + Point& operator=(Point&&) noexcept = default; + int x = 0; + int y = 0; + }; + + struct Rect + { + Rect() = default; + Rect(const Rect&) noexcept = default; + Rect(Rect&&) noexcept = default; + Rect(int x, int y, int width, int height) + : x(x), y(y), width(width), height(height) + { + } + Rect operator*(double rhs) const + { + return { x, y, static_cast(width * rhs), static_cast(height * rhs) }; + } + int area() const noexcept + { + return width * height; + } + Rect center_zoom(double scale, int max_width = INT_MAX, int max_height = INT_MAX) const + { + int half_width_scale = static_cast(width * (1 - scale) / 2); + int half_hight_scale = static_cast(height * (1 - scale) / 2); + Rect dst(x + half_width_scale, y + half_hight_scale, + static_cast(width * scale), static_cast(height * scale)); + if (dst.x < 0) { + dst.x = 0; + } + if (dst.y < 0) { + dst.y = 0; + } + if (dst.width + dst.x >= max_width) { + dst.width = max_width - dst.x; + } + if (dst.height + dst.y >= max_height) { + dst.height = max_height - dst.y; + } + return dst; + } + Rect& operator=(const Rect&) noexcept = default; + Rect& operator=(Rect&&) noexcept = default; + bool empty() const noexcept { return width == 0 || height == 0; } + bool operator==(const Rect& rhs) const noexcept + { + return x == rhs.x && y == rhs.y && width == rhs.width && height == rhs.height; + } + bool include(const Rect& rhs) const noexcept + { + return x <= rhs.x && y <= rhs.y && (x + width) >= (rhs.x + rhs.width) && (y + height) >= (rhs.y + rhs.height); + } + std::string to_string() const + { + return "[ " + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(width) + ", " + std::to_string(height) + " ]"; + } + + int x = 0; + int y = 0; + int width = 0; + int height = 0; + }; + + struct TextRect + { + TextRect() = default; + TextRect(const TextRect&) = default; + TextRect(TextRect&&) noexcept = default; + + explicit operator std::string() const noexcept { return text; } + explicit operator Rect() const noexcept { return rect; } + std::string to_string() const + { + return text + " : " + rect.to_string() + ", score: " + std::to_string(score); + } + TextRect& operator=(const TextRect&) = default; + TextRect& operator=(TextRect&&) noexcept = default; + bool operator==(const TextRect& rhs) const noexcept + { + return text == rhs.text && rect == rhs.rect; + } + + std::string text; + Rect rect; + float score; + }; + using TextRectProc = std::function; + + enum class AlgorithmType + { + Invaild = -1, + JustReturn, + MatchTemplate, + CompareHist, + OcrDetect + }; + + struct MatchRect + { + MatchRect() = default; + MatchRect(const MatchRect&) = default; + MatchRect(MatchRect&&) noexcept = default; + + explicit operator Rect() const noexcept { return rect; } + MatchRect& operator=(const MatchRect&) = default; + MatchRect& operator=(MatchRect&&) noexcept = default; + + AlgorithmType algorithm = AlgorithmType::Invaild; + double score = 0.0; + Rect rect; + }; +} + +namespace std +{ + template <> + class hash + { + public: + size_t operator()(const asst::Rect& rect) const + { + return std::hash()(rect.x) ^ std::hash()(rect.y) ^ std::hash()(rect.width) ^ std::hash()(rect.height); + } + }; + template <> + class hash + { + public: + size_t operator()(const asst::TextRect& tr) const + { + return std::hash()(tr.text) ^ std::hash()(tr.rect); + } + }; +} + +namespace asst +{ + enum class ProcessTaskAction + { + Invalid = 0, + BasicClick = 0x100, + ClickSelf = BasicClick | 1, // 点击自身位置 + ClickRect = BasicClick | 2, // 点击指定区域 + ClickRand = BasicClick | 4, // 点击随机区域 + DoNothing = 0x200, // 什么都不做 + Stop = 0x400, // 停止当前Task + StageDrops = 0x800, // 关卡结束,特化动作 + BasicSwipe = 0x1000, + SwipeToTheLeft = BasicSwipe | 1, // 往左划一下 + SwipeToTheRight = BasicSwipe | 2, // 往右划一下 + }; + + // 任务信息 + struct TaskInfo + { + virtual ~TaskInfo() = default; + std::string name; // 任务名 + AlgorithmType algorithm = // 图像算法类型 + AlgorithmType::Invaild; + ProcessTaskAction action = // 要进行的操作 + ProcessTaskAction::Invalid; + std::vector next; // 下一个可能的任务(列表) + int max_times = INT_MAX; // 任务最多执行多少次 + std::vector exceeded_next; // 达到最多次数了之后,下一个可能的任务(列表) + std::vector reduce_other_times; // 执行了该任务后,需要减少别的任务的执行次数。例如执行了吃理智药,则说明上一次点击蓝色开始行动按钮没生效,所以蓝色开始行动要-1 + asst::Rect specific_rect; // 指定区域,目前仅针对ClickRect任务有用,会点这个区域 + int pre_delay = 0; // 执行该任务前的延时 + int rear_delay = 0; // 执行该任务后的延时 + int retry_times = INT_MAX; // 未找到图像时的重试次数 + Rect roi; // 要识别的区域,若为0则全图识别 + Rect rect_move; // 识别结果移动:有些结果识别到的,和要点击的不是同一个位置。即识别到了res,点击res + result_move的位置 + bool cache = false; // 是否使用缓存区域 + Rect region_of_appeared; // 缓存区域:上次识别成功过,本次只在这个rect里识别,省点性能 + }; + + // 文字识别任务的信息 + struct OcrTaskInfo : public TaskInfo + { + virtual ~OcrTaskInfo() = default; + std::vector text; // 文字的容器,匹配到这里面任一个,就算匹配上了 + bool need_full_match = false; // 是否需要全匹配,否则搜索到子串就算匹配上了 + std::unordered_map + replace_map; // 部分文字容易识别错,字符串强制replace之后,再进行匹配 + }; + + // 图片匹配任务的信息 + struct MatchTaskInfo : public TaskInfo + { + virtual ~MatchTaskInfo() = default; + std::string templ_name; // 匹配模板图片文件名 + double templ_threshold = 0; // 模板匹配阈值 + double special_threshold = 0; // 某些任务使用的特殊的阈值 + std::pair mask_range; // 掩码的二值化范围 + }; + + struct HandleInfo + { + std::string class_name; + std::string window_name; + }; + + struct AdbCmd + { + std::string path; + std::vector addresses; // 会优先尝试连接addresses中的地址,若均失败,则会使用devices获取地址 + std::string devices; + std::string address_regex; + std::string connect; + std::string click; + std::string swipe; + std::string display; + std::string display_format; + std::string screencap; + std::string release; + //std::string pullscreen; + int display_width = 0; + int display_height = 0; + }; + + struct EmulatorInfo + { + std::string name; + HandleInfo handle; + AdbCmd adb; + std::string path; + }; +} diff --git a/src/MeoAssistant/AsstUtils.hpp b/src/MeoAssistant/AsstUtils.hpp index 720a6982cc..ba7f091b29 100644 --- a/src/MeoAssistant/AsstUtils.hpp +++ b/src/MeoAssistant/AsstUtils.hpp @@ -1,317 +1,317 @@ -#pragma once - -#include -#include -#include - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#include -#include -#include -#endif - -namespace asst -{ - namespace utils - { - static std::string string_replace_all(const std::string& src, const std::string& old_value, const std::string& new_value) - { - std::string str = src; - for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) { - if ((pos = str.find(old_value, pos)) != std::string::npos) - str.replace(pos, old_value.length(), new_value); - else - break; - } - return str; - } - - static std::vector string_split(const std::string& str, const std::string& delimiter) - { - std::string::size_type pos1 = 0; - std::string::size_type pos2 = str.find(delimiter); - std::vector result; - - while (std::string::npos != pos2) { - result.emplace_back(str.substr(pos1, pos2 - pos1)); - - pos1 = pos2 + delimiter.size(); - pos2 = str.find(delimiter, pos1); - } - if (pos1 != str.length()) - result.emplace_back(str.substr(pos1)); - - return result; - } - - static std::string get_format_time() - { - char buff[128] = { 0 }; -#ifdef _WIN32 - SYSTEMTIME curtime; - GetLocalTime(&curtime); -#ifdef _MSC_VER - sprintf_s(buff, sizeof(buff), -#else // ! _MSC_VER - sprintf(buff, -#endif // END _MSC_VER - "%04d-%02d-%02d %02d:%02d:%02d.%03d", - curtime.wYear, curtime.wMonth, curtime.wDay, - curtime.wHour, curtime.wMinute, curtime.wSecond, curtime.wMilliseconds); - -#else // ! _WIN32 - struct timeval tv = { 0 }; - gettimeofday(&tv, NULL); - time_t nowtime = tv.tv_sec; - struct tm* tm_info = localtime(&nowtime); - strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", tm_info); - sprintf(buff, "%s.%03ld", buff, tv.tv_usec / 1000); -#endif // END _WIN32 - return buff; - } - - static std::string gbk_2_utf8(const std::string& gbk_str) - { -#ifdef _WIN32 - const char* src_str = gbk_str.c_str(); - int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0); - wchar_t* wstr = new wchar_t[len + 1]; - memset(wstr, 0, len + 1); - MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len); - len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); - char* str = new char[len + 1]; - memset(str, 0, len + 1); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); - std::string strTemp = str; - if (wstr) - delete[] wstr; - if (str) - delete[] str; - return strTemp; -#else // Don't fucking use gbk in linux! - return gbk_str; -#endif - } - - static std::string utf8_to_gbk(const std::string& utf8_str) - { -#ifdef _WIN32 - const char* src_str = utf8_str.c_str(); - int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0); - wchar_t* wszGBK = new wchar_t[len + 1]; - memset(wszGBK, 0, len * 2 + 2); - MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len); - len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); - char* szGBK = new char[len + 1]; - memset(szGBK, 0, len + 1); - WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); - std::string strTemp(szGBK); - if (wszGBK) - delete[] wszGBK; - if (szGBK) - delete[] szGBK; - return strTemp; -#else // Don't fucking use gbk in linux! - return utf8_str; -#endif - } - - template - constexpr inline RetTy make_rect(const ArgType& rect) - { - return RetTy{ rect.x, rect.y, rect.width, rect.height }; - } - - static std::string load_file_without_bom(const std::string& filename) - { - std::ifstream ifs(filename, std::ios::in); - if (!ifs.is_open()) { - return std::string(); - } - std::stringstream iss; - iss << ifs.rdbuf(); - ifs.close(); - std::string str = iss.str(); - - using uchar = unsigned char; - constexpr static uchar Bom_0 = 0xEF; - constexpr static uchar Bom_1 = 0xBB; - constexpr static uchar Bom_2 = 0xBF; - - if (str.size() >= 3 && static_cast(str.at(0)) == Bom_0 && static_cast(str.at(1)) == Bom_1 && static_cast(str.at(2)) == Bom_2) { - str.assign(str.begin() + 3, str.end()); - return str; - } - return str; - } - - static int hamming(std::string hash1, std::string hash2) - { - constexpr static int HammingFlags = 64; - - hash1.insert(hash1.begin(), HammingFlags - hash1.size(), '0'); - hash2.insert(hash2.begin(), HammingFlags - hash2.size(), '0'); - int dist = 0; - for (int i = 0; i < HammingFlags; i = i + 16) { - unsigned long long x = strtoull(hash1.substr(i, 16).c_str(), NULL, 16) ^ strtoull(hash2.substr(i, 16).c_str(), NULL, 16); - while (x) { - dist++; - x = x & (x - 1); - } - } - return dist; - } - - static std::string callcmd(const std::string& cmdline) - { - constexpr int BuffSize = 4096; - std::string pipe_str; -#ifdef _WIN32 - SECURITY_ATTRIBUTES pipe_sec_attr = { 0 }; - pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - pipe_sec_attr.lpSecurityDescriptor = nullptr; - pipe_sec_attr.bInheritHandle = TRUE; - HANDLE pipe_read = nullptr; - HANDLE pipe_child_write = nullptr; - CreatePipe(&pipe_read, &pipe_child_write, &pipe_sec_attr, BuffSize); - - STARTUPINFOA si = { 0 }; - si.cb = sizeof(STARTUPINFO); - si.dwFlags = STARTF_USESTDHANDLES; - si.wShowWindow = SW_HIDE; - si.hStdOutput = pipe_child_write; - si.hStdError = pipe_child_write; - - PROCESS_INFORMATION pi = { 0 }; - - BOOL p_ret = CreateProcessA(NULL, const_cast(cmdline.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); - if (p_ret) { - DWORD read_num = 0; - DWORD std_num = 0; - do { - while (::PeekNamedPipe(pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { - char* pipe_buffer = new char[read_num]; - BOOL read_ret = ::ReadFile(pipe_read, pipe_buffer, read_num, &std_num, NULL); - if (read_ret) { - pipe_str.append(pipe_buffer, pipe_buffer + std_num); - } - if (pipe_buffer != nullptr) { - delete[] pipe_buffer; - pipe_buffer = nullptr; - } - } - } while (::WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT); - - DWORD exit_ret = 255; - ::GetExitCodeProcess(pi.hProcess, &exit_ret); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - - ::CloseHandle(pipe_read); - ::CloseHandle(pipe_child_write); - -#else - constexpr static int PIPE_READ = 0; - constexpr static int PIPE_WRITE = 1; - int pipe_in[2] = { 0 }; - int pipe_out[2] = { 0 }; - int pipe_in_ret = pipe(pipe_in); - int pipe_out_ret = pipe(pipe_out); - fcntl(pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); - int exit_ret = 0; - int child = fork(); - if (child == 0) { - // child process - dup2(pipe_in[PIPE_READ], STDIN_FILENO); - dup2(pipe_out[PIPE_WRITE], STDOUT_FILENO); - dup2(pipe_out[PIPE_WRITE], STDERR_FILENO); - - // all these are for use by parent only - close(pipe_in[PIPE_READ]); - close(pipe_in[PIPE_WRITE]); - close(pipe_out[PIPE_READ]); - close(pipe_out[PIPE_WRITE]); - - exit_ret = execlp("sh", "sh", "-c", cmdline.c_str(), NULL); - exit(exit_ret); - } - else if (child > 0) { - // parent process - - // close unused file descriptors, these are for child only - close(pipe_in[PIPE_READ]); - close(pipe_out[PIPE_WRITE]); - - std::unique_ptr pipe_buffer(new char[BuffSize + 1]); - do { - memset(pipe_buffer.get(), 0, BuffSize); - ssize_t read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); - - while (read_num > 0) { - pipe_str.append(pipe_buffer.get(), pipe_buffer.get() + read_num); - read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); - }; - } while (::waitpid(child, NULL, WNOHANG) == 0); - - close(pipe_in[PIPE_WRITE]); - close(pipe_out[PIPE_READ]); - } - else { - // failed to create child process - close(pipe_in[PIPE_READ]); - close(pipe_in[PIPE_WRITE]); - close(pipe_out[PIPE_READ]); - close(pipe_out[PIPE_WRITE]); - } -#endif - return pipe_str; - } - - //template::value>::type> - // std::string VectorToString(const std::vector& vector, bool to_gbk = false) { - // if (vector.empty()) { - // return std::string(); - // } - // static const std::string inter = ", "; - // std::string str; - // for (const T& ele : vector) { - // if (to_gbk) { - // str += utils::utf8_to_gbk(ele) + inter; - // } - // else { - // str += ele + inter; - // } - // } - - // if (!str.empty()) { - // str.erase(str.size() - inter.size(), inter.size()); - // } - // return str; - //} - - //template::value>::type> - // std::string VectorToString(const std::vector& vector) { - // if (vector.empty()) { - // return std::string(); - // } - // static const std::string inter = ", "; - // std::string str; - // for (const T& ele : vector) { - // str += std::to_string(ele) + inter; - // } - - // if (!str.empty()) { - // str.erase(str.size() - inter.size(), inter.size()); - // } - // return str; - //} - } -} +#pragma once + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +namespace asst +{ + namespace utils + { + static std::string string_replace_all(const std::string& src, const std::string& old_value, const std::string& new_value) + { + std::string str = src; + for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) { + if ((pos = str.find(old_value, pos)) != std::string::npos) + str.replace(pos, old_value.length(), new_value); + else + break; + } + return str; + } + + static std::vector string_split(const std::string& str, const std::string& delimiter) + { + std::string::size_type pos1 = 0; + std::string::size_type pos2 = str.find(delimiter); + std::vector result; + + while (std::string::npos != pos2) { + result.emplace_back(str.substr(pos1, pos2 - pos1)); + + pos1 = pos2 + delimiter.size(); + pos2 = str.find(delimiter, pos1); + } + if (pos1 != str.length()) + result.emplace_back(str.substr(pos1)); + + return result; + } + + static std::string get_format_time() + { + char buff[128] = { 0 }; +#ifdef _WIN32 + SYSTEMTIME curtime; + GetLocalTime(&curtime); +#ifdef _MSC_VER + sprintf_s(buff, sizeof(buff), +#else // ! _MSC_VER + sprintf(buff, +#endif // END _MSC_VER + "%04d-%02d-%02d %02d:%02d:%02d.%03d", + curtime.wYear, curtime.wMonth, curtime.wDay, + curtime.wHour, curtime.wMinute, curtime.wSecond, curtime.wMilliseconds); + +#else // ! _WIN32 + struct timeval tv = { 0 }; + gettimeofday(&tv, NULL); + time_t nowtime = tv.tv_sec; + struct tm* tm_info = localtime(&nowtime); + strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", tm_info); + sprintf(buff, "%s.%03ld", buff, tv.tv_usec / 1000); +#endif // END _WIN32 + return buff; + } + + static std::string gbk_2_utf8(const std::string& gbk_str) + { +#ifdef _WIN32 + const char* src_str = gbk_str.c_str(); + int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0); + wchar_t* wstr = new wchar_t[len + 1]; + memset(wstr, 0, len + 1); + MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len); + len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + char* str = new char[len + 1]; + memset(str, 0, len + 1); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); + std::string strTemp = str; + if (wstr) + delete[] wstr; + if (str) + delete[] str; + return strTemp; +#else // Don't fucking use gbk in linux! + return gbk_str; +#endif + } + + static std::string utf8_to_gbk(const std::string& utf8_str) + { +#ifdef _WIN32 + const char* src_str = utf8_str.c_str(); + int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0); + wchar_t* wszGBK = new wchar_t[len + 1]; + memset(wszGBK, 0, len * 2 + 2); + MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len); + len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); + char* szGBK = new char[len + 1]; + memset(szGBK, 0, len + 1); + WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); + std::string strTemp(szGBK); + if (wszGBK) + delete[] wszGBK; + if (szGBK) + delete[] szGBK; + return strTemp; +#else // Don't fucking use gbk in linux! + return utf8_str; +#endif + } + + template + constexpr inline RetTy make_rect(const ArgType& rect) + { + return RetTy{ rect.x, rect.y, rect.width, rect.height }; + } + + static std::string load_file_without_bom(const std::string& filename) + { + std::ifstream ifs(filename, std::ios::in); + if (!ifs.is_open()) { + return std::string(); + } + std::stringstream iss; + iss << ifs.rdbuf(); + ifs.close(); + std::string str = iss.str(); + + using uchar = unsigned char; + constexpr static uchar Bom_0 = 0xEF; + constexpr static uchar Bom_1 = 0xBB; + constexpr static uchar Bom_2 = 0xBF; + + if (str.size() >= 3 && static_cast(str.at(0)) == Bom_0 && static_cast(str.at(1)) == Bom_1 && static_cast(str.at(2)) == Bom_2) { + str.assign(str.begin() + 3, str.end()); + return str; + } + return str; + } + + static int hamming(std::string hash1, std::string hash2) + { + constexpr static int HammingFlags = 64; + + hash1.insert(hash1.begin(), HammingFlags - hash1.size(), '0'); + hash2.insert(hash2.begin(), HammingFlags - hash2.size(), '0'); + int dist = 0; + for (int i = 0; i < HammingFlags; i = i + 16) { + unsigned long long x = strtoull(hash1.substr(i, 16).c_str(), NULL, 16) ^ strtoull(hash2.substr(i, 16).c_str(), NULL, 16); + while (x) { + dist++; + x = x & (x - 1); + } + } + return dist; + } + + static std::string callcmd(const std::string& cmdline) + { + constexpr int BuffSize = 4096; + std::string pipe_str; +#ifdef _WIN32 + SECURITY_ATTRIBUTES pipe_sec_attr = { 0 }; + pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + pipe_sec_attr.lpSecurityDescriptor = nullptr; + pipe_sec_attr.bInheritHandle = TRUE; + HANDLE pipe_read = nullptr; + HANDLE pipe_child_write = nullptr; + CreatePipe(&pipe_read, &pipe_child_write, &pipe_sec_attr, BuffSize); + + STARTUPINFOA si = { 0 }; + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdOutput = pipe_child_write; + si.hStdError = pipe_child_write; + + PROCESS_INFORMATION pi = { 0 }; + + BOOL p_ret = CreateProcessA(NULL, const_cast(cmdline.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); + if (p_ret) { + DWORD read_num = 0; + DWORD std_num = 0; + do { + while (::PeekNamedPipe(pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { + char* pipe_buffer = new char[read_num]; + BOOL read_ret = ::ReadFile(pipe_read, pipe_buffer, read_num, &std_num, NULL); + if (read_ret) { + pipe_str.append(pipe_buffer, pipe_buffer + std_num); + } + if (pipe_buffer != nullptr) { + delete[] pipe_buffer; + pipe_buffer = nullptr; + } + } + } while (::WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT); + + DWORD exit_ret = 255; + ::GetExitCodeProcess(pi.hProcess, &exit_ret); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + ::CloseHandle(pipe_read); + ::CloseHandle(pipe_child_write); + +#else + constexpr static int PIPE_READ = 0; + constexpr static int PIPE_WRITE = 1; + int pipe_in[2] = { 0 }; + int pipe_out[2] = { 0 }; + int pipe_in_ret = pipe(pipe_in); + int pipe_out_ret = pipe(pipe_out); + fcntl(pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); + int exit_ret = 0; + int child = fork(); + if (child == 0) { + // child process + dup2(pipe_in[PIPE_READ], STDIN_FILENO); + dup2(pipe_out[PIPE_WRITE], STDOUT_FILENO); + dup2(pipe_out[PIPE_WRITE], STDERR_FILENO); + + // all these are for use by parent only + close(pipe_in[PIPE_READ]); + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); + + exit_ret = execlp("sh", "sh", "-c", cmdline.c_str(), NULL); + exit(exit_ret); + } + else if (child > 0) { + // parent process + + // close unused file descriptors, these are for child only + close(pipe_in[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); + + std::unique_ptr pipe_buffer(new char[BuffSize + 1]); + do { + memset(pipe_buffer.get(), 0, BuffSize); + ssize_t read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); + + while (read_num > 0) { + pipe_str.append(pipe_buffer.get(), pipe_buffer.get() + read_num); + read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); + }; + } while (::waitpid(child, NULL, WNOHANG) == 0); + + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); + } + else { + // failed to create child process + close(pipe_in[PIPE_READ]); + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); + } +#endif + return pipe_str; + } + + //template::value>::type> + // std::string VectorToString(const std::vector& vector, bool to_gbk = false) { + // if (vector.empty()) { + // return std::string(); + // } + // static const std::string inter = ", "; + // std::string str; + // for (const T& ele : vector) { + // if (to_gbk) { + // str += utils::utf8_to_gbk(ele) + inter; + // } + // else { + // str += ele + inter; + // } + // } + + // if (!str.empty()) { + // str.erase(str.size() - inter.size(), inter.size()); + // } + // return str; + //} + + //template::value>::type> + // std::string VectorToString(const std::vector& vector) { + // if (vector.empty()) { + // return std::string(); + // } + // static const std::string inter = ", "; + // std::string str; + // for (const T& ele : vector) { + // str += std::to_string(ele) + inter; + // } + + // if (!str.empty()) { + // str.erase(str.size() - inter.size(), inter.size()); + // } + // return str; + //} + } +} diff --git a/src/MeoAssistant/AutoRecruitTask.cpp b/src/MeoAssistant/AutoRecruitTask.cpp index ac67b9a5e1..1ef47615ea 100644 --- a/src/MeoAssistant/AutoRecruitTask.cpp +++ b/src/MeoAssistant/AutoRecruitTask.cpp @@ -1,96 +1,96 @@ -#include "AutoRecruitTask.h" - -#include "Resource.h" -#include "OcrImageAnalyzer.h" -#include "Controller.h" -#include "RecruitImageAnalyzer.h" -#include "ProcessTask.h" -#include "RecruitTask.h" - -void asst::AutoRecruitTask::set_select_level(std::vector select_level) noexcept -{ - m_select_level = std::move(select_level); -} - -void asst::AutoRecruitTask::set_confirm_level(std::vector confirm_level) noexcept -{ - m_confirm_level = std::move(confirm_level); -} - -void asst::AutoRecruitTask::set_need_refresh(bool need_refresh) noexcept -{ - m_need_refresh = need_refresh; -} - -void asst::AutoRecruitTask::set_max_times(int max_times) noexcept -{ - m_max_times = max_times; -} - -bool asst::AutoRecruitTask::_run() -{ - json::value task_start_json = json::object{ - { "task_type", "RecruitTask" }, - { "task_chain", m_task_chain }, - }; - m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); - - int delay = Resrc.cfg().get_options().task_delay; - - auto image = Ctrler.get_image(); - OcrImageAnalyzer start_analyzer(image); - const auto start_task_ptr = std::dynamic_pointer_cast(Task.get("StartRecruit")); - start_analyzer.set_task_info(*start_task_ptr); - - if (!start_analyzer.analyze()) { - return false; - } - - const auto& start_res = start_analyzer.get_result(); - for (; m_cur_times < start_res.size() && m_cur_times < m_max_times; ++m_cur_times) { - if (need_exit()) { - return false; - } - Rect start_rect = start_res.at(m_cur_times).rect; - Ctrler.click(start_rect); - sleep(delay); - - while (true) { - RecruitTask recurit_task(m_callback, m_callback_arg); - recurit_task.set_retry_times(10); - recurit_task.set_param(m_select_level, true); - recurit_task.set_task_chain(m_task_chain); - - // 识别错误,放弃这个公招位,直接返回 - if (!recurit_task.run()) { - m_callback(AsstMsg::RecruitError, json::value(), m_callback_arg); - click_return_button(); - break; - } - - int maybe_level = recurit_task.get_maybe_level(); - - // 尝试刷新 - if (m_need_refresh && maybe_level == 3 - && !recurit_task.get_has_special_tag() - && recurit_task.get_has_refresh()) { - ProcessTask refresh_task(*this, { "RecruitRefresh" }); - if (refresh_task.run()) { - continue; - } - } - if (std::find(m_confirm_level.cbegin(), m_confirm_level.cend(), maybe_level) != m_confirm_level.cend()) { - ProcessTask confirm_task(*this, { "RecruitConfirm" }); - if (!confirm_task.run()) { - return false; - } - break; - } - else { - click_return_button(); - break; - } - } - } - return true; -} \ No newline at end of file +#include "AutoRecruitTask.h" + +#include "Resource.h" +#include "OcrImageAnalyzer.h" +#include "Controller.h" +#include "RecruitImageAnalyzer.h" +#include "ProcessTask.h" +#include "RecruitTask.h" + +void asst::AutoRecruitTask::set_select_level(std::vector select_level) noexcept +{ + m_select_level = std::move(select_level); +} + +void asst::AutoRecruitTask::set_confirm_level(std::vector confirm_level) noexcept +{ + m_confirm_level = std::move(confirm_level); +} + +void asst::AutoRecruitTask::set_need_refresh(bool need_refresh) noexcept +{ + m_need_refresh = need_refresh; +} + +void asst::AutoRecruitTask::set_max_times(int max_times) noexcept +{ + m_max_times = max_times; +} + +bool asst::AutoRecruitTask::_run() +{ + json::value task_start_json = json::object{ + { "task_type", "RecruitTask" }, + { "task_chain", m_task_chain }, + }; + m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); + + int delay = Resrc.cfg().get_options().task_delay; + + auto image = Ctrler.get_image(); + OcrImageAnalyzer start_analyzer(image); + const auto start_task_ptr = std::dynamic_pointer_cast(Task.get("StartRecruit")); + start_analyzer.set_task_info(*start_task_ptr); + + if (!start_analyzer.analyze()) { + return false; + } + + const auto& start_res = start_analyzer.get_result(); + for (; m_cur_times < start_res.size() && m_cur_times < m_max_times; ++m_cur_times) { + if (need_exit()) { + return false; + } + Rect start_rect = start_res.at(m_cur_times).rect; + Ctrler.click(start_rect); + sleep(delay); + + while (true) { + RecruitTask recurit_task(m_callback, m_callback_arg); + recurit_task.set_retry_times(10); + recurit_task.set_param(m_select_level, true); + recurit_task.set_task_chain(m_task_chain); + + // 识别错误,放弃这个公招位,直接返回 + if (!recurit_task.run()) { + m_callback(AsstMsg::RecruitError, json::value(), m_callback_arg); + click_return_button(); + break; + } + + int maybe_level = recurit_task.get_maybe_level(); + + // 尝试刷新 + if (m_need_refresh && maybe_level == 3 + && !recurit_task.get_has_special_tag() + && recurit_task.get_has_refresh()) { + ProcessTask refresh_task(*this, { "RecruitRefresh" }); + if (refresh_task.run()) { + continue; + } + } + if (std::find(m_confirm_level.cbegin(), m_confirm_level.cend(), maybe_level) != m_confirm_level.cend()) { + ProcessTask confirm_task(*this, { "RecruitConfirm" }); + if (!confirm_task.run()) { + return false; + } + break; + } + else { + click_return_button(); + break; + } + } + } + return true; +} diff --git a/src/MeoAssistant/Controller.cpp b/src/MeoAssistant/Controller.cpp index 3e1bb2986f..22f31de485 100644 --- a/src/MeoAssistant/Controller.cpp +++ b/src/MeoAssistant/Controller.cpp @@ -1,738 +1,738 @@ -#include "Controller.h" - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include - -#include "AsstDef.h" -#include "Logger.hpp" -#include "Resource.h" -#include "UserConfiger.h" - -asst::Controller::Controller() - : m_rand_engine(std::chrono::system_clock::now().time_since_epoch().count()) -{ - LogTraceFunction; - -#ifdef _WIN32 - // 安全属性描述符 - m_pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - m_pipe_sec_attr.lpSecurityDescriptor = nullptr; - m_pipe_sec_attr.bInheritHandle = TRUE; - - // 创建管道,本进程读-子进程写 - BOOL pipe_read_ret = ::CreatePipe(&m_pipe_read, &m_pipe_child_write, &m_pipe_sec_attr, PipeBuffSize); - // 创建管道,本进程写-子进程读 - BOOL pipe_write_ret = ::CreatePipe(&m_pipe_write, &m_pipe_child_read, &m_pipe_sec_attr, PipeBuffSize); - - if (!pipe_read_ret || !pipe_write_ret) { - throw "controller pipe created failed"; - } - - m_child_startup_info.cb = sizeof(STARTUPINFO); - m_child_startup_info.dwFlags = STARTF_USESTDHANDLES; - m_child_startup_info.wShowWindow = SW_HIDE; - // 重定向子进程的读写 - m_child_startup_info.hStdInput = m_pipe_child_read; - m_child_startup_info.hStdOutput = m_pipe_child_write; - m_child_startup_info.hStdError = m_pipe_child_write; -#else - int pipe_in_ret = pipe(m_pipe_in); - int pipe_out_ret = pipe(m_pipe_out); - fcntl(m_pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); - - if (pipe_in_ret < 0 || pipe_out_ret < 0) { - throw "controller pipe created failed"; - } - -#endif - - auto bind_pipe_working_proc = std::bind(&Controller::pipe_working_proc, this); - m_cmd_thread = std::thread(bind_pipe_working_proc); -} - -asst::Controller::~Controller() -{ - LogTraceFunction; - - m_thread_exit = true; - //m_thread_idle = true; - m_cmd_condvar.notify_all(); - m_completed_id = UINT_MAX; // make all WinMacor::wait to exit - - if (m_cmd_thread.joinable()) { - m_cmd_thread.join(); - } - -#ifndef _WIN32 - if (m_child) { -#else - if (true) { -#endif - call_command(m_emulator_info.adb.release); - } - -#ifdef _WIN32 - ::CloseHandle(m_pipe_read); - ::CloseHandle(m_pipe_write); - ::CloseHandle(m_pipe_child_read); - ::CloseHandle(m_pipe_child_write); -#else - close(m_pipe_in[PIPE_READ]); - close(m_pipe_in[PIPE_WRITE]); - close(m_pipe_out[PIPE_READ]); - close(m_pipe_out[PIPE_WRITE]); -#endif - } - -bool asst::Controller::connect_adb(const std::string & address) -{ - LogTraceScope("connect_adb " + address); - - std::string connect_cmd = utils::string_replace_all( - utils::string_replace_all(m_emulator_info.adb.connect, "[Adb]", m_emulator_info.adb.path), - "[Address]", address); - auto&& [connect_ret, connect_result] = call_command(connect_cmd); - // 端口即使错误,命令仍然会返回0,TODO 对connect_result进行判断 - if (!connect_ret) { - return false; - } - - std::string display_cmd = utils::string_replace_all( - utils::string_replace_all(m_emulator_info.adb.display, "[Adb]", m_emulator_info.adb.path), - "[Address]", address); - auto&& [display_ret, display_result] = call_command(display_cmd); - if (!display_ret) { - return false; - } - - std::string display_pipe_str( - std::make_move_iterator(display_result.begin()), - std::make_move_iterator(display_result.end())); - int size_value1 = 0; - int size_value2 = 0; -#ifdef _MSC_VER - sscanf_s(display_pipe_str.c_str(), m_emulator_info.adb.display_format.c_str(), &size_value1, &size_value2); -#else - sscanf(display_pipe_str.c_str(), m_emulator_info.adb.display_format.c_str(), &size_value1, &size_value2); -#endif - // 为了防止抓取句柄的时候手机是竖屏的(还没进游戏),这里取大的值为宽,小的为高 - // 总不能有人竖屏玩明日方舟吧(? - m_emulator_info.adb.display_width = (std::max)(size_value1, size_value2); - m_emulator_info.adb.display_height = (std::min)(size_value1, size_value2); - - constexpr double DefaultRatio = - static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault); - double cur_ratio = static_cast(m_emulator_info.adb.display_width) / static_cast(m_emulator_info.adb.display_height); - - if (cur_ratio >= DefaultRatio // 说明是宽屏或默认16:9,按照高度计算缩放 - || std::fabs(cur_ratio - DefaultRatio) < DoubleDiff) { - int scale_width = cur_ratio * WindowHeightDefault; - m_scale_size = std::make_pair(scale_width, WindowHeightDefault); - m_control_scale = static_cast(m_emulator_info.adb.display_height) / static_cast(WindowHeightDefault); - } - else { // 否则可能是偏正方形的屏幕,按宽度计算 - int scale_height = WindowWidthDefault / cur_ratio; - m_scale_size = std::make_pair(WindowWidthDefault, scale_height); - m_control_scale = static_cast(m_emulator_info.adb.display_width) / static_cast(WindowWidthDefault); - } - - m_emulator_info.adb.click = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.click, "[Adb]", m_emulator_info.adb.path), "[Address]", address); - m_emulator_info.adb.swipe = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.swipe, "[Adb]", m_emulator_info.adb.path), "[Address]", address); - m_emulator_info.adb.screencap = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.screencap, "[Adb]", m_emulator_info.adb.path), "[Address]", address); - m_emulator_info.adb.release = utils::string_replace_all(m_emulator_info.adb.release, "[Adb]", m_emulator_info.adb.path); - - return true; -} - -asst::Rect asst::Controller::shaped_correct(const Rect & rect) const -{ - if (rect.empty()) { - return rect; - } - // 明日方舟在异形屏上,有的地方是按比例缩放的,有的地方又是直接位移。没法整,这里简单粗暴一点截一个长条 - Rect dst = rect; - if (m_scale_size.first != WindowWidthDefault) { // 说明是宽屏 - if (rect.width <= WindowWidthDefault / 2) { - if (rect.x + rect.width <= WindowWidthDefault / 2) { // 整个矩形都在左半边 - dst.x = 0; - dst.width = m_scale_size.first / 2; - } - else if (rect.x >= WindowWidthDefault / 2) { // 整个矩形都在右半边 - dst.x = m_scale_size.first / 2; - dst.width = m_scale_size.first / 2; - } - else { // 整个矩形横跨了中线 - dst.x = 0; - dst.width = m_scale_size.first; - } - } - else { - dst.x = 0; - dst.width = m_scale_size.first; - } - } - else if (m_scale_size.second != WindowHeightDefault) { // 说明是偏方形屏 - if (rect.height <= WindowHeightDefault / 2) { - if (rect.y + rect.height <= WindowHeightDefault / 2) { // 整个矩形都在上半边 - dst.y = 0; - dst.height = m_scale_size.second / 2; - } - else if (rect.y >= WindowHeightDefault / 2) { // 整个矩形都在下半边 - dst.y = m_scale_size.second / 2; - dst.height = m_scale_size.second / 2; // 整个矩形横跨了中线 - } - else { - dst.y = 0; - dst.height = m_scale_size.second; - } - } - - else { - dst.y = 0; - dst.height = m_scale_size.second; - } - } - return dst; -} - -std::pair asst::Controller::get_scale_size() const noexcept -{ - return m_scale_size; -} - -void asst::Controller::pipe_working_proc() -{ - LogTraceFunction; - - while (!m_thread_exit) { - std::unique_lock cmd_queue_lock(m_cmd_queue_mutex); - - if (!m_cmd_queue.empty()) { // 队列中有任务就执行任务 - std::string cmd = m_cmd_queue.front(); - m_cmd_queue.pop(); - cmd_queue_lock.unlock(); - // todo 判断命令是否执行成功 - call_command(cmd); - ++m_completed_id; - } - //else if (!m_thread_idle) { // 队列中没有任务,又不是闲置的时候,就去截图 - // cmd_queue_lock.unlock(); - // auto start_time = std::chrono::steady_clock::now(); - // screencap(); - // cmd_queue_lock.lock(); - // if (!m_cmd_queue.empty()) { - // continue; - // } - // m_cmd_condvar.wait_until( - // cmd_queue_lock, - // start_time + std::chrono::milliseconds(1000)); // todo 时间写到配置文件里 - //} - else { - //m_cmd_condvar.wait(cmd_queue_lock, [&]() -> bool {return !m_thread_idle; }); - m_cmd_condvar.wait(cmd_queue_lock); - } - } -} - -void asst::Controller::set_dirname(std::string dirname) noexcept -{ - m_dirname = std::move(dirname); -} - -bool asst::Controller::try_capture(const EmulatorInfo & info, bool without_handle) -{ - LogTraceScope("try_capture | " + info.name); - - const HandleInfo& handle_info = info.handle; - - std::string adb_path; - if (!without_handle) { // 使用模拟器自带的adb -#ifdef _WIN32 // Only support Windows - // 转成宽字符的 - wchar_t* class_wbuff = nullptr; - if (!handle_info.class_name.empty()) { - size_t class_len = (handle_info.class_name.size() + 1) * 2; - class_wbuff = new wchar_t[class_len]; - ::MultiByteToWideChar(CP_UTF8, 0, handle_info.class_name.c_str(), -1, class_wbuff, class_len); - } - wchar_t* window_wbuff = nullptr; - if (!handle_info.window_name.empty()) { - size_t window_len = (handle_info.window_name.size() + 1) * 2; - window_wbuff = new wchar_t[window_len]; - memset(window_wbuff, 0, window_len); - ::MultiByteToWideChar(CP_UTF8, 0, handle_info.window_name.c_str(), -1, window_wbuff, window_len); - } - // 查找窗口句柄 - HWND window_handle = nullptr; - window_handle = ::FindWindowExW(window_handle, nullptr, class_wbuff, window_wbuff); - - if (class_wbuff != nullptr) { - delete[] class_wbuff; - class_wbuff = nullptr; - } - if (window_wbuff != nullptr) { - delete[] window_wbuff; - window_wbuff = nullptr; - } - if (window_handle == nullptr) { - return false; - } - - std::string emulator_path; - if (info.path.empty()) { - // 获取模拟器窗口句柄对应的进程句柄 - DWORD process_id = 0; - ::GetWindowThreadProcessId(window_handle, &process_id); - HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id); - - // 获取模拟器程序所在路径 - LPSTR path_buff = new CHAR[MAX_PATH]; - memset(path_buff, 0, MAX_PATH); - DWORD path_size = MAX_PATH; - QueryFullProcessImageNameA(process_handle, 0, path_buff, &path_size); - emulator_path = std::string(path_buff); - if (path_buff != nullptr) { - delete[] path_buff; - path_buff = nullptr; - } - if (emulator_path.empty()) { - return false; - } - Resrc.cfg().set_emulator_path(info.name, emulator_path); - Resrc.user().set_emulator_path(info.name, emulator_path); - } - else { - emulator_path = info.path; - } - - // 到这一步说明句柄和权限没问题了,接下来就是adb的事情了 - m_emulator_info = info; - Log.trace("Handle:", window_handle, "Name:", m_emulator_info.name); - - adb_path = emulator_path.substr(0, emulator_path.find_last_of('\\') + 1); - adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[EmulatorPath]", adb_path) + '"'; - adb_path = utils::string_replace_all(adb_path, "[ExecDir]", m_dirname); -#else - Log.error("Capture handle is only supported on Windows!"); - return false; -#endif - } - else { // 使用辅助自带的标准adb - m_emulator_info = info; -#ifdef _WIN32 - adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[ExecDir]", m_dirname) + '"'; -#else - adb_path = m_emulator_info.adb.path; -#endif - } - - m_emulator_info.adb.path = std::move(adb_path); - - // 优先使用addresses里指定的地址 - for (const std::string& address : info.adb.addresses) { - if (connect_adb(address)) { - return true; - } - } - - // 若指定地址都没连上,再尝试用devices查找地址 - std::string devices_cmd = utils::string_replace_all(m_emulator_info.adb.devices, "[Adb]", m_emulator_info.adb.path); - auto&& [devices_ret, devices_result] = call_command(devices_cmd); - if (!devices_ret) { - return false; - } - std::string devices_pipe_str( - std::make_move_iterator(devices_result.begin()), - std::make_move_iterator(devices_result.end())); - auto lines = utils::string_split(devices_pipe_str, "\r\n"); - - std::string address; - const std::regex address_regex(m_emulator_info.adb.address_regex); - for (const std::string& line : lines) { - std::smatch smatch; - if (std::regex_match(line, smatch, address_regex)) { - address = smatch[1]; - } - } - Log.trace("device address", address); - if (address.empty()) { - return false; - } - - return connect_adb(address); -} - -//void asst::Controller::set_idle(bool flag) -//{ -// LogTraceFunction; -// -// m_thread_idle = flag; -// if (!flag) { -// // 开始前,立即截一张图,保证第一张图片非空 -// //screencap(); -// -// m_cmd_condvar.notify_one(); -// } -//} - -std::pair> asst::Controller::call_command(const std::string & cmd) -{ - LogTraceFunction; - - static std::mutex pipe_mutex; - std::unique_lock pipe_lock(pipe_mutex); - - std::vector pipe_data; - -#ifdef _WIN32 - PROCESS_INFORMATION process_info = { 0 }; // 进程信息结构体 - ::CreateProcessA(NULL, const_cast(cmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &m_child_startup_info, &process_info); - - do { - //DWORD write_num = 0; - //WriteFile(parent_write, cmd.c_str(), cmd.size(), &write_num, NULL); - - DWORD read_num = 0; - DWORD std_num = 0; - while (::PeekNamedPipe(m_pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { - uchar* pipe_buffer = new uchar[read_num]; - BOOL read_ret = ::ReadFile(m_pipe_read, pipe_buffer, read_num, &std_num, NULL); - if (read_ret) { - pipe_data.insert(pipe_data.end(), pipe_buffer, pipe_buffer + std_num); - } - if (pipe_buffer != nullptr) { - delete[] pipe_buffer; - pipe_buffer = nullptr; - } - } - } while (::WaitForSingleObject(process_info.hProcess, 0) == WAIT_TIMEOUT); - - DWORD exit_ret = -1; - ::GetExitCodeProcess(process_info.hProcess, &exit_ret); - - ::CloseHandle(process_info.hProcess); - ::CloseHandle(process_info.hThread); - -#else - int exit_ret = 0; - m_child = fork(); - if (m_child == 0) { - // child process - LogTraceScope("Child process: " + cmd); - std::cout << ("Child process: " + cmd) << std::endl; - - dup2(m_pipe_in[PIPE_READ], STDIN_FILENO); - dup2(m_pipe_out[PIPE_WRITE], STDOUT_FILENO); - dup2(m_pipe_out[PIPE_WRITE], STDERR_FILENO); - - // all these are for use by parent only - // close(m_pipe_in[PIPE_READ]); - // close(m_pipe_in[PIPE_WRITE]); - // close(m_pipe_out[PIPE_READ]); - // close(m_pipe_out[PIPE_WRITE]); - - exit_ret = execlp("sh", "sh", "-c", cmd.c_str(), NULL); - exit(exit_ret); - } - else if (m_child > 0) { - // parent process - // LogTraceScope("Parent process: " + cmd); - constexpr int BuffSize = 4096; - std::unique_ptr pipe_buffer(new uchar[BuffSize + 1]); - do { - memset(pipe_buffer.get(), 0, BuffSize); - ssize_t read_num = read(m_pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); - - while (read_num > 0) { - pipe_data.insert(pipe_data.end(), pipe_buffer.get(), pipe_buffer.get() + read_num); - read_num = read(m_pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); - }; - } while (::waitpid(m_child, NULL, WNOHANG) == 0); - } - else { - // failed to create child process - } -#endif - - Log.trace("Call `", cmd, "` ret", exit_ret, ", output size:", pipe_data.size()); - if (!pipe_data.empty() && pipe_data.size() < 4096) { - Log.trace("output:", std::string(pipe_data.cbegin(), pipe_data.cend())); - } - - return make_pair(!exit_ret, std::move(pipe_data)); -} - -void asst::Controller::convert_lf(std::vector&data) -{ - LogTraceFunction; - - if (data.empty() || data.size() <= 1) { - return; - } - using Iter = std::vector::iterator; - auto pred = [](const Iter& cur) -> bool { - return *cur == '\r' && *(cur + 1) == '\n'; - }; - // find the first of "\r\n" - Iter first_iter; - for (Iter iter = data.begin(); iter != data.end() - 1; ++iter) { - if (pred(iter)) { - first_iter = iter; - break; - } - } - // move forward all non-crlf elements - Iter end_r1_iter = data.end() - 1; - Iter next_iter = first_iter; - while (++first_iter != end_r1_iter) { - if (!pred(first_iter)) { - *next_iter = std::move(*first_iter); - ++next_iter; - } - } - *next_iter = std::move(*end_r1_iter); - ++next_iter; - data.erase(next_iter, data.end()); -} - -asst::Point asst::Controller::rand_point_in_rect(const Rect & rect) -{ - int x = 0, y = 0; - if (rect.width == 0) { - x = rect.x; - } - else { - int x_rand = std::poisson_distribution(rect.width / 2)(m_rand_engine); - - x = x_rand + rect.x; - } - - if (rect.height == 0) { - y = rect.y; - } - else { - int y_rand = std::poisson_distribution(rect.height / 2)(m_rand_engine); - y = y_rand + rect.y; - } - - return Point(x, y); -} - -void asst::Controller::random_delay() const -{ - auto& opt = Resrc.cfg().get_options(); - if (opt.control_delay_upper != 0) { - LogTraceFunction; - static std::default_random_engine rand_engine( - std::chrono::steady_clock::now().time_since_epoch().count()); - static std::uniform_int_distribution rand_uni( - opt.control_delay_lower, - opt.control_delay_upper); - - unsigned rand_delay = rand_uni(rand_engine); - - Log.trace("random_delay |", rand_delay, "ms"); - std::this_thread::sleep_for(std::chrono::milliseconds(rand_delay)); - } -} - -int asst::Controller::push_cmd(const std::string & cmd) -{ - random_delay(); - - std::unique_lock lock(m_cmd_queue_mutex); - m_cmd_queue.emplace(cmd); - m_cmd_condvar.notify_one(); - return ++m_push_id; -} - -void asst::Controller::wait(unsigned id) const noexcept -{ - while (id > m_completed_id) { - std::this_thread::yield(); - } -} - -bool asst::Controller::screencap() -{ - LogTraceFunction; - -#if 1 - auto&& [ret, data] = call_command(m_emulator_info.adb.screencap); - if (ret && !data.empty()) { - if (m_image_convert_lf) { - convert_lf(data); - } - m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); - if (m_cache_image.empty()) { - Log.info("Data is not empty, but image is empty, try to convert lf"); - convert_lf(data); - m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); - if (m_cache_image.empty()) { - m_image_convert_lf = false; - Log.error("convert lf and retry decode falied!"); - return false; - } - m_image_convert_lf = true; - return true; - } - return true; - } - else { - Log.error("Data is empty!"); - return false; - } -#else - m_cache_image = cv::imread("/home/mreo/test.png"); - return true; -#endif - - //cv::Mat temp_image = cv::imdecode(data, cv::IMREAD_COLOR); - ////std::unique_lock image_lock(m_image_mutex); - //m_cache_image = std::move(temp_image); -} - -int asst::Controller::click(const Point & p, bool block) -{ - int x = p.x * m_control_scale; - int y = p.y * m_control_scale; - //log.trace("Click, raw:", p.x, p.y, "corr:", x, y); - - return click_without_scale(Point(x, y), block); -} - -int asst::Controller::click(const Rect & rect, bool block) -{ - return click(rand_point_in_rect(rect), block); -} - -int asst::Controller::click_without_scale(const Point & p, bool block) -{ - if (p.x < 0 || p.x >= m_emulator_info.adb.display_width || p.y < 0 || p.y >= m_emulator_info.adb.display_height) { - Log.error("click point out of range"); - } - std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.click, "[x]", std::to_string(p.x)); - cur_cmd = utils::string_replace_all(cur_cmd, "[y]", std::to_string(p.y)); - int id = push_cmd(cur_cmd); - if (block) { - wait(id); - } - return id; -} - -int asst::Controller::click_without_scale(const Rect & rect, bool block) -{ - return click_without_scale(rand_point_in_rect(rect), block); -} - -int asst::Controller::swipe(const Point & p1, const Point & p2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - int x1 = p1.x * m_control_scale; - int y1 = p1.y * m_control_scale; - int x2 = p2.x * m_control_scale; - int y2 = p2.y * m_control_scale; - //log.trace("Swipe, raw:", p1.x, p1.y, p2.x, p2.y, "corr:", x1, y1, x2, y2); - - return swipe_without_scale(Point(x1, y1), Point(x2, y2), duration, block, extra_delay, extra_swipe); -} - -int asst::Controller::swipe(const Rect & r1, const Rect & r2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - return swipe(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); -} - -int asst::Controller::swipe_without_scale(const Point & p1, const Point & p2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - if (p1.x < 0 || p1.x >= m_emulator_info.adb.display_width || p1.y < 0 || p1.y >= m_emulator_info.adb.display_height || p2.x < 0 || p2.x >= m_emulator_info.adb.display_width || p2.y < 0 || p2.y >= m_emulator_info.adb.display_height) { - Log.error("swipe point out of range"); - } - std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p1.x)); - cur_cmd = utils::string_replace_all(cur_cmd, "[y1]", std::to_string(p1.y)); - cur_cmd = utils::string_replace_all(cur_cmd, "[x2]", std::to_string(p2.x)); - cur_cmd = utils::string_replace_all(cur_cmd, "[y2]", std::to_string(p2.y)); - if (duration <= 0) { - cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", ""); - } - else { - cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", std::to_string(duration)); - } - - int id = 0; - - int extra_swipe_dist = Resrc.cfg().get_options().adb_extra_swipe_dist /* * m_control_scale*/; - int extra_swipe_duration = Resrc.cfg().get_options().adb_extra_swipe_duration; - - // 额外的滑动:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 - if (extra_swipe && extra_swipe_duration >= 0) { - std::string extra_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p2.x)); - extra_cmd = utils::string_replace_all(extra_cmd, "[y1]", std::to_string(p2.y)); - int end_x = 0, end_y = 0; - if (p2.x != p1.x) { - double k = (double)(p2.y - p1.y) / (p2.x - p1.x); - double temp = extra_swipe_dist / std::sqrt(1 + k * k); - end_x = p2.x + (p2.x > p1.x ? -1 : 1) * temp; - end_y = p2.y + (p2.y > p1.y ? -1 : 1) * std::fabs(k) * temp; - } - else { - end_x = p2.x; - end_y = p2.y + (p2.y > p1.y ? -1 : 1) * extra_swipe_dist; - } - extra_cmd = utils::string_replace_all(extra_cmd, "[x2]", std::to_string(end_x)); - extra_cmd = utils::string_replace_all(extra_cmd, "[y2]", std::to_string(end_y)); - extra_cmd = utils::string_replace_all(extra_cmd, "[duration]", std::to_string(extra_swipe_duration)); - - push_cmd(cur_cmd); - id = push_cmd(extra_cmd); - } - else { - id = push_cmd(cur_cmd); - } - - if (block) { - wait(id); - std::this_thread::sleep_for(std::chrono::milliseconds(extra_delay)); - } - return id; -} - -int asst::Controller::swipe_without_scale(const Rect & r1, const Rect & r2, int duration, bool block, int extra_delay, bool extra_swipe) -{ - return swipe_without_scale(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); -} - -cv::Mat asst::Controller::get_image(bool raw) -{ - // 有些模拟器adb偶尔会莫名其妙截图失败,多试几次 - for (int i = 0; i != 20; ++i) { - if (screencap()) { - break; - } - } - //std::shared_lock image_lock(m_image_mutex); - if (raw) { - return m_cache_image; - } - else { - if (m_cache_image.empty()) { - return m_cache_image; - } - cv::Mat resized_mat; - const static cv::Size dsize(m_scale_size.first, m_scale_size.second); - cv::resize(m_cache_image, resized_mat, dsize); - return resized_mat; - } -} +#include "Controller.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "AsstDef.h" +#include "Logger.hpp" +#include "Resource.h" +#include "UserConfiger.h" + +asst::Controller::Controller() + : m_rand_engine(std::chrono::system_clock::now().time_since_epoch().count()) +{ + LogTraceFunction; + +#ifdef _WIN32 + // 安全属性描述符 + m_pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + m_pipe_sec_attr.lpSecurityDescriptor = nullptr; + m_pipe_sec_attr.bInheritHandle = TRUE; + + // 创建管道,本进程读-子进程写 + BOOL pipe_read_ret = ::CreatePipe(&m_pipe_read, &m_pipe_child_write, &m_pipe_sec_attr, PipeBuffSize); + // 创建管道,本进程写-子进程读 + BOOL pipe_write_ret = ::CreatePipe(&m_pipe_write, &m_pipe_child_read, &m_pipe_sec_attr, PipeBuffSize); + + if (!pipe_read_ret || !pipe_write_ret) { + throw "controller pipe created failed"; + } + + m_child_startup_info.cb = sizeof(STARTUPINFO); + m_child_startup_info.dwFlags = STARTF_USESTDHANDLES; + m_child_startup_info.wShowWindow = SW_HIDE; + // 重定向子进程的读写 + m_child_startup_info.hStdInput = m_pipe_child_read; + m_child_startup_info.hStdOutput = m_pipe_child_write; + m_child_startup_info.hStdError = m_pipe_child_write; +#else + int pipe_in_ret = pipe(m_pipe_in); + int pipe_out_ret = pipe(m_pipe_out); + fcntl(m_pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); + + if (pipe_in_ret < 0 || pipe_out_ret < 0) { + throw "controller pipe created failed"; + } + +#endif + + auto bind_pipe_working_proc = std::bind(&Controller::pipe_working_proc, this); + m_cmd_thread = std::thread(bind_pipe_working_proc); +} + +asst::Controller::~Controller() +{ + LogTraceFunction; + + m_thread_exit = true; + //m_thread_idle = true; + m_cmd_condvar.notify_all(); + m_completed_id = UINT_MAX; // make all WinMacor::wait to exit + + if (m_cmd_thread.joinable()) { + m_cmd_thread.join(); + } + +#ifndef _WIN32 + if (m_child) { +#else + if (true) { +#endif + call_command(m_emulator_info.adb.release); + } + +#ifdef _WIN32 + ::CloseHandle(m_pipe_read); + ::CloseHandle(m_pipe_write); + ::CloseHandle(m_pipe_child_read); + ::CloseHandle(m_pipe_child_write); +#else + close(m_pipe_in[PIPE_READ]); + close(m_pipe_in[PIPE_WRITE]); + close(m_pipe_out[PIPE_READ]); + close(m_pipe_out[PIPE_WRITE]); +#endif + } + +bool asst::Controller::connect_adb(const std::string & address) +{ + LogTraceScope("connect_adb " + address); + + std::string connect_cmd = utils::string_replace_all( + utils::string_replace_all(m_emulator_info.adb.connect, "[Adb]", m_emulator_info.adb.path), + "[Address]", address); + auto&& [connect_ret, connect_result] = call_command(connect_cmd); + // 端口即使错误,命令仍然会返回0,TODO 对connect_result进行判断 + if (!connect_ret) { + return false; + } + + std::string display_cmd = utils::string_replace_all( + utils::string_replace_all(m_emulator_info.adb.display, "[Adb]", m_emulator_info.adb.path), + "[Address]", address); + auto&& [display_ret, display_result] = call_command(display_cmd); + if (!display_ret) { + return false; + } + + std::string display_pipe_str( + std::make_move_iterator(display_result.begin()), + std::make_move_iterator(display_result.end())); + int size_value1 = 0; + int size_value2 = 0; +#ifdef _MSC_VER + sscanf_s(display_pipe_str.c_str(), m_emulator_info.adb.display_format.c_str(), &size_value1, &size_value2); +#else + sscanf(display_pipe_str.c_str(), m_emulator_info.adb.display_format.c_str(), &size_value1, &size_value2); +#endif + // 为了防止抓取句柄的时候手机是竖屏的(还没进游戏),这里取大的值为宽,小的为高 + // 总不能有人竖屏玩明日方舟吧(? + m_emulator_info.adb.display_width = (std::max)(size_value1, size_value2); + m_emulator_info.adb.display_height = (std::min)(size_value1, size_value2); + + constexpr double DefaultRatio = + static_cast(WindowWidthDefault) / static_cast(WindowHeightDefault); + double cur_ratio = static_cast(m_emulator_info.adb.display_width) / static_cast(m_emulator_info.adb.display_height); + + if (cur_ratio >= DefaultRatio // 说明是宽屏或默认16:9,按照高度计算缩放 + || std::fabs(cur_ratio - DefaultRatio) < DoubleDiff) { + int scale_width = cur_ratio * WindowHeightDefault; + m_scale_size = std::make_pair(scale_width, WindowHeightDefault); + m_control_scale = static_cast(m_emulator_info.adb.display_height) / static_cast(WindowHeightDefault); + } + else { // 否则可能是偏正方形的屏幕,按宽度计算 + int scale_height = WindowWidthDefault / cur_ratio; + m_scale_size = std::make_pair(WindowWidthDefault, scale_height); + m_control_scale = static_cast(m_emulator_info.adb.display_width) / static_cast(WindowWidthDefault); + } + + m_emulator_info.adb.click = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.click, "[Adb]", m_emulator_info.adb.path), "[Address]", address); + m_emulator_info.adb.swipe = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.swipe, "[Adb]", m_emulator_info.adb.path), "[Address]", address); + m_emulator_info.adb.screencap = utils::string_replace_all(utils::string_replace_all(m_emulator_info.adb.screencap, "[Adb]", m_emulator_info.adb.path), "[Address]", address); + m_emulator_info.adb.release = utils::string_replace_all(m_emulator_info.adb.release, "[Adb]", m_emulator_info.adb.path); + + return true; +} + +asst::Rect asst::Controller::shaped_correct(const Rect & rect) const +{ + if (rect.empty()) { + return rect; + } + // 明日方舟在异形屏上,有的地方是按比例缩放的,有的地方又是直接位移。没法整,这里简单粗暴一点截一个长条 + Rect dst = rect; + if (m_scale_size.first != WindowWidthDefault) { // 说明是宽屏 + if (rect.width <= WindowWidthDefault / 2) { + if (rect.x + rect.width <= WindowWidthDefault / 2) { // 整个矩形都在左半边 + dst.x = 0; + dst.width = m_scale_size.first / 2; + } + else if (rect.x >= WindowWidthDefault / 2) { // 整个矩形都在右半边 + dst.x = m_scale_size.first / 2; + dst.width = m_scale_size.first / 2; + } + else { // 整个矩形横跨了中线 + dst.x = 0; + dst.width = m_scale_size.first; + } + } + else { + dst.x = 0; + dst.width = m_scale_size.first; + } + } + else if (m_scale_size.second != WindowHeightDefault) { // 说明是偏方形屏 + if (rect.height <= WindowHeightDefault / 2) { + if (rect.y + rect.height <= WindowHeightDefault / 2) { // 整个矩形都在上半边 + dst.y = 0; + dst.height = m_scale_size.second / 2; + } + else if (rect.y >= WindowHeightDefault / 2) { // 整个矩形都在下半边 + dst.y = m_scale_size.second / 2; + dst.height = m_scale_size.second / 2; // 整个矩形横跨了中线 + } + else { + dst.y = 0; + dst.height = m_scale_size.second; + } + } + + else { + dst.y = 0; + dst.height = m_scale_size.second; + } + } + return dst; +} + +std::pair asst::Controller::get_scale_size() const noexcept +{ + return m_scale_size; +} + +void asst::Controller::pipe_working_proc() +{ + LogTraceFunction; + + while (!m_thread_exit) { + std::unique_lock cmd_queue_lock(m_cmd_queue_mutex); + + if (!m_cmd_queue.empty()) { // 队列中有任务就执行任务 + std::string cmd = m_cmd_queue.front(); + m_cmd_queue.pop(); + cmd_queue_lock.unlock(); + // todo 判断命令是否执行成功 + call_command(cmd); + ++m_completed_id; + } + //else if (!m_thread_idle) { // 队列中没有任务,又不是闲置的时候,就去截图 + // cmd_queue_lock.unlock(); + // auto start_time = std::chrono::steady_clock::now(); + // screencap(); + // cmd_queue_lock.lock(); + // if (!m_cmd_queue.empty()) { + // continue; + // } + // m_cmd_condvar.wait_until( + // cmd_queue_lock, + // start_time + std::chrono::milliseconds(1000)); // todo 时间写到配置文件里 + //} + else { + //m_cmd_condvar.wait(cmd_queue_lock, [&]() -> bool {return !m_thread_idle; }); + m_cmd_condvar.wait(cmd_queue_lock); + } + } +} + +void asst::Controller::set_dirname(std::string dirname) noexcept +{ + m_dirname = std::move(dirname); +} + +bool asst::Controller::try_capture(const EmulatorInfo & info, bool without_handle) +{ + LogTraceScope("try_capture | " + info.name); + + const HandleInfo& handle_info = info.handle; + + std::string adb_path; + if (!without_handle) { // 使用模拟器自带的adb +#ifdef _WIN32 // Only support Windows + // 转成宽字符的 + wchar_t* class_wbuff = nullptr; + if (!handle_info.class_name.empty()) { + size_t class_len = (handle_info.class_name.size() + 1) * 2; + class_wbuff = new wchar_t[class_len]; + ::MultiByteToWideChar(CP_UTF8, 0, handle_info.class_name.c_str(), -1, class_wbuff, class_len); + } + wchar_t* window_wbuff = nullptr; + if (!handle_info.window_name.empty()) { + size_t window_len = (handle_info.window_name.size() + 1) * 2; + window_wbuff = new wchar_t[window_len]; + memset(window_wbuff, 0, window_len); + ::MultiByteToWideChar(CP_UTF8, 0, handle_info.window_name.c_str(), -1, window_wbuff, window_len); + } + // 查找窗口句柄 + HWND window_handle = nullptr; + window_handle = ::FindWindowExW(window_handle, nullptr, class_wbuff, window_wbuff); + + if (class_wbuff != nullptr) { + delete[] class_wbuff; + class_wbuff = nullptr; + } + if (window_wbuff != nullptr) { + delete[] window_wbuff; + window_wbuff = nullptr; + } + if (window_handle == nullptr) { + return false; + } + + std::string emulator_path; + if (info.path.empty()) { + // 获取模拟器窗口句柄对应的进程句柄 + DWORD process_id = 0; + ::GetWindowThreadProcessId(window_handle, &process_id); + HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id); + + // 获取模拟器程序所在路径 + LPSTR path_buff = new CHAR[MAX_PATH]; + memset(path_buff, 0, MAX_PATH); + DWORD path_size = MAX_PATH; + QueryFullProcessImageNameA(process_handle, 0, path_buff, &path_size); + emulator_path = std::string(path_buff); + if (path_buff != nullptr) { + delete[] path_buff; + path_buff = nullptr; + } + if (emulator_path.empty()) { + return false; + } + Resrc.cfg().set_emulator_path(info.name, emulator_path); + Resrc.user().set_emulator_path(info.name, emulator_path); + } + else { + emulator_path = info.path; + } + + // 到这一步说明句柄和权限没问题了,接下来就是adb的事情了 + m_emulator_info = info; + Log.trace("Handle:", window_handle, "Name:", m_emulator_info.name); + + adb_path = emulator_path.substr(0, emulator_path.find_last_of('\\') + 1); + adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[EmulatorPath]", adb_path) + '"'; + adb_path = utils::string_replace_all(adb_path, "[ExecDir]", m_dirname); +#else + Log.error("Capture handle is only supported on Windows!"); + return false; +#endif + } + else { // 使用辅助自带的标准adb + m_emulator_info = info; +#ifdef _WIN32 + adb_path = '"' + utils::string_replace_all(m_emulator_info.adb.path, "[ExecDir]", m_dirname) + '"'; +#else + adb_path = m_emulator_info.adb.path; +#endif + } + + m_emulator_info.adb.path = std::move(adb_path); + + // 优先使用addresses里指定的地址 + for (const std::string& address : info.adb.addresses) { + if (connect_adb(address)) { + return true; + } + } + + // 若指定地址都没连上,再尝试用devices查找地址 + std::string devices_cmd = utils::string_replace_all(m_emulator_info.adb.devices, "[Adb]", m_emulator_info.adb.path); + auto&& [devices_ret, devices_result] = call_command(devices_cmd); + if (!devices_ret) { + return false; + } + std::string devices_pipe_str( + std::make_move_iterator(devices_result.begin()), + std::make_move_iterator(devices_result.end())); + auto lines = utils::string_split(devices_pipe_str, "\r\n"); + + std::string address; + const std::regex address_regex(m_emulator_info.adb.address_regex); + for (const std::string& line : lines) { + std::smatch smatch; + if (std::regex_match(line, smatch, address_regex)) { + address = smatch[1]; + } + } + Log.trace("device address", address); + if (address.empty()) { + return false; + } + + return connect_adb(address); +} + +//void asst::Controller::set_idle(bool flag) +//{ +// LogTraceFunction; +// +// m_thread_idle = flag; +// if (!flag) { +// // 开始前,立即截一张图,保证第一张图片非空 +// //screencap(); +// +// m_cmd_condvar.notify_one(); +// } +//} + +std::pair> asst::Controller::call_command(const std::string & cmd) +{ + LogTraceFunction; + + static std::mutex pipe_mutex; + std::unique_lock pipe_lock(pipe_mutex); + + std::vector pipe_data; + +#ifdef _WIN32 + PROCESS_INFORMATION process_info = { 0 }; // 进程信息结构体 + ::CreateProcessA(NULL, const_cast(cmd.c_str()), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &m_child_startup_info, &process_info); + + do { + //DWORD write_num = 0; + //WriteFile(parent_write, cmd.c_str(), cmd.size(), &write_num, NULL); + + DWORD read_num = 0; + DWORD std_num = 0; + while (::PeekNamedPipe(m_pipe_read, NULL, 0, NULL, &read_num, NULL) && read_num > 0) { + uchar* pipe_buffer = new uchar[read_num]; + BOOL read_ret = ::ReadFile(m_pipe_read, pipe_buffer, read_num, &std_num, NULL); + if (read_ret) { + pipe_data.insert(pipe_data.end(), pipe_buffer, pipe_buffer + std_num); + } + if (pipe_buffer != nullptr) { + delete[] pipe_buffer; + pipe_buffer = nullptr; + } + } + } while (::WaitForSingleObject(process_info.hProcess, 0) == WAIT_TIMEOUT); + + DWORD exit_ret = -1; + ::GetExitCodeProcess(process_info.hProcess, &exit_ret); + + ::CloseHandle(process_info.hProcess); + ::CloseHandle(process_info.hThread); + +#else + int exit_ret = 0; + m_child = fork(); + if (m_child == 0) { + // child process + LogTraceScope("Child process: " + cmd); + std::cout << ("Child process: " + cmd) << std::endl; + + dup2(m_pipe_in[PIPE_READ], STDIN_FILENO); + dup2(m_pipe_out[PIPE_WRITE], STDOUT_FILENO); + dup2(m_pipe_out[PIPE_WRITE], STDERR_FILENO); + + // all these are for use by parent only + // close(m_pipe_in[PIPE_READ]); + // close(m_pipe_in[PIPE_WRITE]); + // close(m_pipe_out[PIPE_READ]); + // close(m_pipe_out[PIPE_WRITE]); + + exit_ret = execlp("sh", "sh", "-c", cmd.c_str(), NULL); + exit(exit_ret); + } + else if (m_child > 0) { + // parent process + // LogTraceScope("Parent process: " + cmd); + constexpr int BuffSize = 4096; + std::unique_ptr pipe_buffer(new uchar[BuffSize + 1]); + do { + memset(pipe_buffer.get(), 0, BuffSize); + ssize_t read_num = read(m_pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); + + while (read_num > 0) { + pipe_data.insert(pipe_data.end(), pipe_buffer.get(), pipe_buffer.get() + read_num); + read_num = read(m_pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); + }; + } while (::waitpid(m_child, NULL, WNOHANG) == 0); + } + else { + // failed to create child process + } +#endif + + Log.trace("Call `", cmd, "` ret", exit_ret, ", output size:", pipe_data.size()); + if (!pipe_data.empty() && pipe_data.size() < 4096) { + Log.trace("output:", std::string(pipe_data.cbegin(), pipe_data.cend())); + } + + return make_pair(!exit_ret, std::move(pipe_data)); +} + +void asst::Controller::convert_lf(std::vector&data) +{ + LogTraceFunction; + + if (data.empty() || data.size() <= 1) { + return; + } + using Iter = std::vector::iterator; + auto pred = [](const Iter& cur) -> bool { + return *cur == '\r' && *(cur + 1) == '\n'; + }; + // find the first of "\r\n" + Iter first_iter; + for (Iter iter = data.begin(); iter != data.end() - 1; ++iter) { + if (pred(iter)) { + first_iter = iter; + break; + } + } + // move forward all non-crlf elements + Iter end_r1_iter = data.end() - 1; + Iter next_iter = first_iter; + while (++first_iter != end_r1_iter) { + if (!pred(first_iter)) { + *next_iter = std::move(*first_iter); + ++next_iter; + } + } + *next_iter = std::move(*end_r1_iter); + ++next_iter; + data.erase(next_iter, data.end()); +} + +asst::Point asst::Controller::rand_point_in_rect(const Rect & rect) +{ + int x = 0, y = 0; + if (rect.width == 0) { + x = rect.x; + } + else { + int x_rand = std::poisson_distribution(rect.width / 2)(m_rand_engine); + + x = x_rand + rect.x; + } + + if (rect.height == 0) { + y = rect.y; + } + else { + int y_rand = std::poisson_distribution(rect.height / 2)(m_rand_engine); + y = y_rand + rect.y; + } + + return Point(x, y); +} + +void asst::Controller::random_delay() const +{ + auto& opt = Resrc.cfg().get_options(); + if (opt.control_delay_upper != 0) { + LogTraceFunction; + static std::default_random_engine rand_engine( + std::chrono::steady_clock::now().time_since_epoch().count()); + static std::uniform_int_distribution rand_uni( + opt.control_delay_lower, + opt.control_delay_upper); + + unsigned rand_delay = rand_uni(rand_engine); + + Log.trace("random_delay |", rand_delay, "ms"); + std::this_thread::sleep_for(std::chrono::milliseconds(rand_delay)); + } +} + +int asst::Controller::push_cmd(const std::string & cmd) +{ + random_delay(); + + std::unique_lock lock(m_cmd_queue_mutex); + m_cmd_queue.emplace(cmd); + m_cmd_condvar.notify_one(); + return ++m_push_id; +} + +void asst::Controller::wait(unsigned id) const noexcept +{ + while (id > m_completed_id) { + std::this_thread::yield(); + } +} + +bool asst::Controller::screencap() +{ + LogTraceFunction; + +#if 1 + auto&& [ret, data] = call_command(m_emulator_info.adb.screencap); + if (ret && !data.empty()) { + if (m_image_convert_lf) { + convert_lf(data); + } + m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); + if (m_cache_image.empty()) { + Log.info("Data is not empty, but image is empty, try to convert lf"); + convert_lf(data); + m_cache_image = cv::imdecode(data, cv::IMREAD_COLOR); + if (m_cache_image.empty()) { + m_image_convert_lf = false; + Log.error("convert lf and retry decode falied!"); + return false; + } + m_image_convert_lf = true; + return true; + } + return true; + } + else { + Log.error("Data is empty!"); + return false; + } +#else + m_cache_image = cv::imread("/home/mreo/test.png"); + return true; +#endif + + //cv::Mat temp_image = cv::imdecode(data, cv::IMREAD_COLOR); + ////std::unique_lock image_lock(m_image_mutex); + //m_cache_image = std::move(temp_image); +} + +int asst::Controller::click(const Point & p, bool block) +{ + int x = p.x * m_control_scale; + int y = p.y * m_control_scale; + //log.trace("Click, raw:", p.x, p.y, "corr:", x, y); + + return click_without_scale(Point(x, y), block); +} + +int asst::Controller::click(const Rect & rect, bool block) +{ + return click(rand_point_in_rect(rect), block); +} + +int asst::Controller::click_without_scale(const Point & p, bool block) +{ + if (p.x < 0 || p.x >= m_emulator_info.adb.display_width || p.y < 0 || p.y >= m_emulator_info.adb.display_height) { + Log.error("click point out of range"); + } + std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.click, "[x]", std::to_string(p.x)); + cur_cmd = utils::string_replace_all(cur_cmd, "[y]", std::to_string(p.y)); + int id = push_cmd(cur_cmd); + if (block) { + wait(id); + } + return id; +} + +int asst::Controller::click_without_scale(const Rect & rect, bool block) +{ + return click_without_scale(rand_point_in_rect(rect), block); +} + +int asst::Controller::swipe(const Point & p1, const Point & p2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + int x1 = p1.x * m_control_scale; + int y1 = p1.y * m_control_scale; + int x2 = p2.x * m_control_scale; + int y2 = p2.y * m_control_scale; + //log.trace("Swipe, raw:", p1.x, p1.y, p2.x, p2.y, "corr:", x1, y1, x2, y2); + + return swipe_without_scale(Point(x1, y1), Point(x2, y2), duration, block, extra_delay, extra_swipe); +} + +int asst::Controller::swipe(const Rect & r1, const Rect & r2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + return swipe(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); +} + +int asst::Controller::swipe_without_scale(const Point & p1, const Point & p2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + if (p1.x < 0 || p1.x >= m_emulator_info.adb.display_width || p1.y < 0 || p1.y >= m_emulator_info.adb.display_height || p2.x < 0 || p2.x >= m_emulator_info.adb.display_width || p2.y < 0 || p2.y >= m_emulator_info.adb.display_height) { + Log.error("swipe point out of range"); + } + std::string cur_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p1.x)); + cur_cmd = utils::string_replace_all(cur_cmd, "[y1]", std::to_string(p1.y)); + cur_cmd = utils::string_replace_all(cur_cmd, "[x2]", std::to_string(p2.x)); + cur_cmd = utils::string_replace_all(cur_cmd, "[y2]", std::to_string(p2.y)); + if (duration <= 0) { + cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", ""); + } + else { + cur_cmd = utils::string_replace_all(cur_cmd, "[duration]", std::to_string(duration)); + } + + int id = 0; + + int extra_swipe_dist = Resrc.cfg().get_options().adb_extra_swipe_dist /* * m_control_scale*/; + int extra_swipe_duration = Resrc.cfg().get_options().adb_extra_swipe_duration; + + // 额外的滑动:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 + if (extra_swipe && extra_swipe_duration >= 0) { + std::string extra_cmd = utils::string_replace_all(m_emulator_info.adb.swipe, "[x1]", std::to_string(p2.x)); + extra_cmd = utils::string_replace_all(extra_cmd, "[y1]", std::to_string(p2.y)); + int end_x = 0, end_y = 0; + if (p2.x != p1.x) { + double k = (double)(p2.y - p1.y) / (p2.x - p1.x); + double temp = extra_swipe_dist / std::sqrt(1 + k * k); + end_x = p2.x + (p2.x > p1.x ? -1 : 1) * temp; + end_y = p2.y + (p2.y > p1.y ? -1 : 1) * std::fabs(k) * temp; + } + else { + end_x = p2.x; + end_y = p2.y + (p2.y > p1.y ? -1 : 1) * extra_swipe_dist; + } + extra_cmd = utils::string_replace_all(extra_cmd, "[x2]", std::to_string(end_x)); + extra_cmd = utils::string_replace_all(extra_cmd, "[y2]", std::to_string(end_y)); + extra_cmd = utils::string_replace_all(extra_cmd, "[duration]", std::to_string(extra_swipe_duration)); + + push_cmd(cur_cmd); + id = push_cmd(extra_cmd); + } + else { + id = push_cmd(cur_cmd); + } + + if (block) { + wait(id); + std::this_thread::sleep_for(std::chrono::milliseconds(extra_delay)); + } + return id; +} + +int asst::Controller::swipe_without_scale(const Rect & r1, const Rect & r2, int duration, bool block, int extra_delay, bool extra_swipe) +{ + return swipe_without_scale(rand_point_in_rect(r1), rand_point_in_rect(r2), duration, block, extra_delay, extra_swipe); +} + +cv::Mat asst::Controller::get_image(bool raw) +{ + // 有些模拟器adb偶尔会莫名其妙截图失败,多试几次 + for (int i = 0; i != 20; ++i) { + if (screencap()) { + break; + } + } + //std::shared_lock image_lock(m_image_mutex); + if (raw) { + return m_cache_image; + } + else { + if (m_cache_image.empty()) { + return m_cache_image; + } + cv::Mat resized_mat; + const static cv::Size dsize(m_scale_size.first, m_scale_size.second); + cv::resize(m_cache_image, resized_mat, dsize); + return resized_mat; + } +} diff --git a/src/MeoAssistant/Controller.h b/src/MeoAssistant/Controller.h index 2cd1d85ff4..a0a9b195fa 100644 --- a/src/MeoAssistant/Controller.h +++ b/src/MeoAssistant/Controller.h @@ -1,119 +1,119 @@ -#pragma once - -#include -#include -#include - -#include -#include // for pimpl -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -#include - -#include "AsstDef.h" - -namespace asst -{ - class Controller - { - public: - Controller(const Controller&) = delete; - Controller(Controller&&) = delete; - ~Controller(); - - static Controller& get_instance() - { - static Controller unique_instance; - return unique_instance; - } - - static void set_dirname(std::string dirname) noexcept; - - bool try_capture(const EmulatorInfo& info, bool without_handle = false); - cv::Mat get_image(bool raw = false); - - // 点击和滑动都是异步执行,返回该任务的id - int click(const Point& p, bool block = true); - int click(const Rect& rect, bool block = true); - int click_without_scale(const Point& p, bool block = true); - int click_without_scale(const Rect& rect, bool block = true); - - constexpr static int SwipeExtraDelayDefault = 1000; - int swipe(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - int swipe(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - int swipe_without_scale(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - int swipe_without_scale(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); - - void wait(unsigned id) const noexcept; - - // 异形屏矫正 - Rect shaped_correct(const Rect& rect) const; - std::pair get_scale_size() const noexcept; - - Controller& operator=(const Controller&) = delete; - Controller& operator=(Controller&&) = delete; - - private: - Controller(); - - bool connect_adb(const std::string& address); - - void pipe_working_proc(); - std::pair> call_command(const std::string& cmd); - int push_cmd(const std::string& cmd); - bool screencap(); - Point rand_point_in_rect(const Rect& rect); - - void random_delay() const; - - // 转换data中所有的crlf为lf:有些模拟器自带的adb,exec-out输出的\n,会被替换成\r\n,导致解码错误,所以这里转一下回来(点名批评mumu) - static void convert_lf(std::vector& data); - - inline static std::string m_dirname; - - bool m_thread_exit = false; - //bool m_thread_idle = true; - std::thread m_cmd_thread; - std::mutex m_cmd_queue_mutex; - std::condition_variable m_cmd_condvar; - std::queue m_cmd_queue; - std::atomic m_completed_id = 0; - unsigned m_push_id = 0; // push_id的自增总是伴随着queue的push,肯定是要上锁的,所以没必要原子 - - //std::shared_mutex m_image_mutex; - cv::Mat m_cache_image; - bool m_image_convert_lf = false; - - constexpr static int PipeBuffSize = 1048576; // 管道缓冲区大小 -#ifdef _WIN32 - HANDLE m_pipe_read = nullptr; // 读管道句柄 - HANDLE m_pipe_write = nullptr; // 写管道句柄 - HANDLE m_pipe_child_read = nullptr; // 子进程的读管道句柄 - HANDLE m_pipe_child_write = nullptr; // 子进程的写管道句柄 - SECURITY_ATTRIBUTES m_pipe_sec_attr = { 0 }; // 管道安全描述符 - STARTUPINFOA m_child_startup_info = { 0 }; // 子进程启动信息 -#else - constexpr static int PIPE_READ = 0; - constexpr static int PIPE_WRITE = 1; - int m_pipe_in[2] = { 0 }; - int m_pipe_out[2] = { 0 }; - int m_child = 0; -#endif - - EmulatorInfo m_emulator_info; - std::minstd_rand m_rand_engine; - std::pair m_scale_size; - double m_control_scale = 1.0; - }; - - //static auto& ctrler = Controller::get_instance(); -#define Ctrler Controller::get_instance() -} +#pragma once + +#include +#include +#include + +#include +#include // for pimpl +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include + +#include "AsstDef.h" + +namespace asst +{ + class Controller + { + public: + Controller(const Controller&) = delete; + Controller(Controller&&) = delete; + ~Controller(); + + static Controller& get_instance() + { + static Controller unique_instance; + return unique_instance; + } + + static void set_dirname(std::string dirname) noexcept; + + bool try_capture(const EmulatorInfo& info, bool without_handle = false); + cv::Mat get_image(bool raw = false); + + // 点击和滑动都是异步执行,返回该任务的id + int click(const Point& p, bool block = true); + int click(const Rect& rect, bool block = true); + int click_without_scale(const Point& p, bool block = true); + int click_without_scale(const Rect& rect, bool block = true); + + constexpr static int SwipeExtraDelayDefault = 1000; + int swipe(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + int swipe(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + int swipe_without_scale(const Point& p1, const Point& p2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + int swipe_without_scale(const Rect& r1, const Rect& r2, int duration = 0, bool block = true, int extra_delay = SwipeExtraDelayDefault, bool extra_swipe = false); + + void wait(unsigned id) const noexcept; + + // 异形屏矫正 + Rect shaped_correct(const Rect& rect) const; + std::pair get_scale_size() const noexcept; + + Controller& operator=(const Controller&) = delete; + Controller& operator=(Controller&&) = delete; + + private: + Controller(); + + bool connect_adb(const std::string& address); + + void pipe_working_proc(); + std::pair> call_command(const std::string& cmd); + int push_cmd(const std::string& cmd); + bool screencap(); + Point rand_point_in_rect(const Rect& rect); + + void random_delay() const; + + // 转换data中所有的crlf为lf:有些模拟器自带的adb,exec-out输出的\n,会被替换成\r\n,导致解码错误,所以这里转一下回来(点名批评mumu) + static void convert_lf(std::vector& data); + + inline static std::string m_dirname; + + bool m_thread_exit = false; + //bool m_thread_idle = true; + std::thread m_cmd_thread; + std::mutex m_cmd_queue_mutex; + std::condition_variable m_cmd_condvar; + std::queue m_cmd_queue; + std::atomic m_completed_id = 0; + unsigned m_push_id = 0; // push_id的自增总是伴随着queue的push,肯定是要上锁的,所以没必要原子 + + //std::shared_mutex m_image_mutex; + cv::Mat m_cache_image; + bool m_image_convert_lf = false; + + constexpr static int PipeBuffSize = 1048576; // 管道缓冲区大小 +#ifdef _WIN32 + HANDLE m_pipe_read = nullptr; // 读管道句柄 + HANDLE m_pipe_write = nullptr; // 写管道句柄 + HANDLE m_pipe_child_read = nullptr; // 子进程的读管道句柄 + HANDLE m_pipe_child_write = nullptr; // 子进程的写管道句柄 + SECURITY_ATTRIBUTES m_pipe_sec_attr = { 0 }; // 管道安全描述符 + STARTUPINFOA m_child_startup_info = { 0 }; // 子进程启动信息 +#else + constexpr static int PIPE_READ = 0; + constexpr static int PIPE_WRITE = 1; + int m_pipe_in[2] = { 0 }; + int m_pipe_out[2] = { 0 }; + int m_child = 0; +#endif + + EmulatorInfo m_emulator_info; + std::minstd_rand m_rand_engine; + std::pair m_scale_size; + double m_control_scale = 1.0; + }; + + //static auto& ctrler = Controller::get_instance(); +#define Ctrler Controller::get_instance() +} diff --git a/src/MeoAssistant/CreditShoppingTask.cpp b/src/MeoAssistant/CreditShoppingTask.cpp index 2b22825100..23e0ceecf1 100644 --- a/src/MeoAssistant/CreditShoppingTask.cpp +++ b/src/MeoAssistant/CreditShoppingTask.cpp @@ -80,4 +80,4 @@ bool asst::CreditShoppingTask::_run() } return true; -} \ No newline at end of file +} diff --git a/src/MeoAssistant/GeneralConfiger.cpp b/src/MeoAssistant/GeneralConfiger.cpp index a81da41122..a1b30ff47f 100644 --- a/src/MeoAssistant/GeneralConfiger.cpp +++ b/src/MeoAssistant/GeneralConfiger.cpp @@ -1,62 +1,62 @@ -#include "GeneralConfiger.h" - -#include - -bool asst::GeneralConfiger::parse(const json::value& json) -{ - m_version = json.at("version").as_string(); - - { - const json::value& options_json = json.at("options"); - m_options.connect_type = static_cast(options_json.at("connectType").as_integer()); - m_options.task_delay = options_json.at("taskDelay").as_integer(); - m_options.control_delay_lower = options_json.at("controlDelayRange")[0].as_integer(); - m_options.control_delay_upper = options_json.at("controlDelayRange")[1].as_integer(); - //m_options.print_window = options_json.at("printWindow").as_boolean(); - m_options.adb_extra_swipe_dist = options_json.get("adbExtraSwipeDist", 100); - m_options.adb_extra_swipe_duration = options_json.get("adbExtraSwipeDuration", -1); - - auto& penguin_report = options_json.at("penguinReport"); - m_options.penguin_report.enable = penguin_report.get("enable", true); - m_options.penguin_report.cmd_format = penguin_report.get("cmdFormat", std::string()); - m_options.penguin_report.server = penguin_report.get("server", "CN"); - - if (options_json.exist("aipOcr")) { - auto& aip_ocr = options_json.at("aipOcr"); - m_options.aip_ocr.enable = aip_ocr.get("enable", false); - m_options.aip_ocr.accurate = aip_ocr.get("accurate", false); - m_options.aip_ocr.client_id = aip_ocr.get("clientId", std::string()); - m_options.aip_ocr.client_secret = aip_ocr.get("clientSerect", std::string()); - } - } - - for (const auto& [name, emulator_json] : json.at("emulator").as_object()) { - EmulatorInfo emulator_info; - emulator_info.name = name; - - const json::object& handle_json = emulator_json.at("handle").as_object(); - emulator_info.handle.class_name = handle_json.at("class").as_string(); - emulator_info.handle.window_name = handle_json.at("window").as_string(); - - const json::object& adb_json = emulator_json.at("adb").as_object(); - emulator_info.adb.path = adb_json.at("path").as_string(); - - for (const json::value& address_json : adb_json.at("addresses").as_array()) { - emulator_info.adb.addresses.emplace_back(address_json.as_string()); - } - emulator_info.adb.devices = adb_json.at("devices").as_string(); - emulator_info.adb.address_regex = adb_json.at("addressRegex").as_string(); - emulator_info.adb.connect = adb_json.at("connect").as_string(); - emulator_info.adb.click = adb_json.at("click").as_string(); - emulator_info.adb.swipe = adb_json.at("swipe").as_string(); - emulator_info.adb.display = adb_json.at("display").as_string(); - emulator_info.adb.display_format = adb_json.at("displayFormat").as_string(); - emulator_info.adb.screencap = adb_json.at("screencap").as_string(); - emulator_info.adb.release = adb_json.at("release").as_string(); - //emulator_info.adb.pullscreen = adb_json.at("pullscreen").as_string(); - - m_emulators_info.emplace(name, std::move(emulator_info)); - } - - return true; -} +#include "GeneralConfiger.h" + +#include + +bool asst::GeneralConfiger::parse(const json::value& json) +{ + m_version = json.at("version").as_string(); + + { + const json::value& options_json = json.at("options"); + m_options.connect_type = static_cast(options_json.at("connectType").as_integer()); + m_options.task_delay = options_json.at("taskDelay").as_integer(); + m_options.control_delay_lower = options_json.at("controlDelayRange")[0].as_integer(); + m_options.control_delay_upper = options_json.at("controlDelayRange")[1].as_integer(); + //m_options.print_window = options_json.at("printWindow").as_boolean(); + m_options.adb_extra_swipe_dist = options_json.get("adbExtraSwipeDist", 100); + m_options.adb_extra_swipe_duration = options_json.get("adbExtraSwipeDuration", -1); + + auto& penguin_report = options_json.at("penguinReport"); + m_options.penguin_report.enable = penguin_report.get("enable", true); + m_options.penguin_report.cmd_format = penguin_report.get("cmdFormat", std::string()); + m_options.penguin_report.server = penguin_report.get("server", "CN"); + + if (options_json.exist("aipOcr")) { + auto& aip_ocr = options_json.at("aipOcr"); + m_options.aip_ocr.enable = aip_ocr.get("enable", false); + m_options.aip_ocr.accurate = aip_ocr.get("accurate", false); + m_options.aip_ocr.client_id = aip_ocr.get("clientId", std::string()); + m_options.aip_ocr.client_secret = aip_ocr.get("clientSerect", std::string()); + } + } + + for (const auto& [name, emulator_json] : json.at("emulator").as_object()) { + EmulatorInfo emulator_info; + emulator_info.name = name; + + const json::object& handle_json = emulator_json.at("handle").as_object(); + emulator_info.handle.class_name = handle_json.at("class").as_string(); + emulator_info.handle.window_name = handle_json.at("window").as_string(); + + const json::object& adb_json = emulator_json.at("adb").as_object(); + emulator_info.adb.path = adb_json.at("path").as_string(); + + for (const json::value& address_json : adb_json.at("addresses").as_array()) { + emulator_info.adb.addresses.emplace_back(address_json.as_string()); + } + emulator_info.adb.devices = adb_json.at("devices").as_string(); + emulator_info.adb.address_regex = adb_json.at("addressRegex").as_string(); + emulator_info.adb.connect = adb_json.at("connect").as_string(); + emulator_info.adb.click = adb_json.at("click").as_string(); + emulator_info.adb.swipe = adb_json.at("swipe").as_string(); + emulator_info.adb.display = adb_json.at("display").as_string(); + emulator_info.adb.display_format = adb_json.at("displayFormat").as_string(); + emulator_info.adb.screencap = adb_json.at("screencap").as_string(); + emulator_info.adb.release = adb_json.at("release").as_string(); + //emulator_info.adb.pullscreen = adb_json.at("pullscreen").as_string(); + + m_emulators_info.emplace(name, std::move(emulator_info)); + } + + return true; +} diff --git a/src/MeoAssistant/GeneralConfiger.h b/src/MeoAssistant/GeneralConfiger.h index 62332061b2..7e3cecc28d 100644 --- a/src/MeoAssistant/GeneralConfiger.h +++ b/src/MeoAssistant/GeneralConfiger.h @@ -1,90 +1,90 @@ -#pragma once - -#include "AbstractConfiger.h" - -#include -#include -#include - -#include "AsstDef.h" - -namespace asst -{ - enum class ConnectType - { - Emulator, - Custom - }; - - struct AipOcrCfg // 百度 OCR API 的配置 - { - bool enable = false; - bool accurate = false; // 是否使用高精度识别 - std::string client_id; - std::string client_secret; - }; - - struct PenguinReportCfg // 企鹅物流数据汇报 的配置 - { - bool enable = false; - std::string cmd_format; // 命令格式 - std::string extra_param; // 额外参数 - std::string server; // 上传参数中"server"字段,"CN", "US", "JP" and "KR". - }; - - struct Options - { - ConnectType connect_type = ConnectType::Emulator; // 连接类型 - int task_delay = 0; // 任务间延时:越快操作越快,但会增加CPU消耗 - int control_delay_lower = 0; // 点击随机延时下限:每次点击操作会进行随机延时 - int control_delay_upper = 0; // 点击随机延时上限:每次点击操作会进行随机延时 - //bool print_window = false; // 截图功能:开启后每次结算界面会截图到screenshot目录下 - int adb_extra_swipe_dist = 0; // 额外的滑动距离:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 - int adb_extra_swipe_duration = -1; // 额外的滑动持续时间:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来。若小于0,则关闭额外滑动功能 - PenguinReportCfg penguin_report; // 企鹅数据汇报:每次到结算界面,汇报掉落数据至企鹅数据 https://penguin-stats.cn/ - AipOcrCfg aip_ocr; // 百度 OCR API 的配置 - }; - - class GeneralConfiger : public AbstractConfiger - { - public: - virtual ~GeneralConfiger() = default; - - const std::string& get_version() const noexcept - { - return m_version; - } - const Options& get_options() const noexcept - { - return m_options; - } - Options& get_options() - { - return m_options; - } - const std::unordered_map& get_emulators_info() const noexcept - { - return m_emulators_info; - } - - void set_options(Options opt) noexcept - { - m_options = std::move(opt); - } - void set_emulator_info(std::string name, EmulatorInfo emu) - { - m_emulators_info.emplace(std::move(name), std::move(emu)); - } - void set_emulator_path(std::string name, std::string path) - { - m_emulators_info[name].path = path; - } - - protected: - virtual bool parse(const json::value& json) override; - - std::string m_version; - Options m_options; - std::unordered_map m_emulators_info; - }; -} +#pragma once + +#include "AbstractConfiger.h" + +#include +#include +#include + +#include "AsstDef.h" + +namespace asst +{ + enum class ConnectType + { + Emulator, + Custom + }; + + struct AipOcrCfg // 百度 OCR API 的配置 + { + bool enable = false; + bool accurate = false; // 是否使用高精度识别 + std::string client_id; + std::string client_secret; + }; + + struct PenguinReportCfg // 企鹅物流数据汇报 的配置 + { + bool enable = false; + std::string cmd_format; // 命令格式 + std::string extra_param; // 额外参数 + std::string server; // 上传参数中"server"字段,"CN", "US", "JP" and "KR". + }; + + struct Options + { + ConnectType connect_type = ConnectType::Emulator; // 连接类型 + int task_delay = 0; // 任务间延时:越快操作越快,但会增加CPU消耗 + int control_delay_lower = 0; // 点击随机延时下限:每次点击操作会进行随机延时 + int control_delay_upper = 0; // 点击随机延时上限:每次点击操作会进行随机延时 + //bool print_window = false; // 截图功能:开启后每次结算界面会截图到screenshot目录下 + int adb_extra_swipe_dist = 0; // 额外的滑动距离:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 + int adb_extra_swipe_duration = -1; // 额外的滑动持续时间:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来。若小于0,则关闭额外滑动功能 + PenguinReportCfg penguin_report; // 企鹅数据汇报:每次到结算界面,汇报掉落数据至企鹅数据 https://penguin-stats.cn/ + AipOcrCfg aip_ocr; // 百度 OCR API 的配置 + }; + + class GeneralConfiger : public AbstractConfiger + { + public: + virtual ~GeneralConfiger() = default; + + const std::string& get_version() const noexcept + { + return m_version; + } + const Options& get_options() const noexcept + { + return m_options; + } + Options& get_options() + { + return m_options; + } + const std::unordered_map& get_emulators_info() const noexcept + { + return m_emulators_info; + } + + void set_options(Options opt) noexcept + { + m_options = std::move(opt); + } + void set_emulator_info(std::string name, EmulatorInfo emu) + { + m_emulators_info.emplace(std::move(name), std::move(emu)); + } + void set_emulator_path(std::string name, std::string path) + { + m_emulators_info[name].path = path; + } + + protected: + virtual bool parse(const json::value& json) override; + + std::string m_version; + Options m_options; + std::unordered_map m_emulators_info; + }; +} diff --git a/src/MeoAssistant/InfrastAbstractTask.cpp b/src/MeoAssistant/InfrastAbstractTask.cpp index a06919f64e..c105cc2c11 100644 --- a/src/MeoAssistant/InfrastAbstractTask.cpp +++ b/src/MeoAssistant/InfrastAbstractTask.cpp @@ -1,248 +1,248 @@ -#include "InfrastAbstractTask.h" - -#include "AsstMsg.h" -#include "Controller.h" -#include "InfrastFacilityImageAnalyzer.h" -#include "Logger.hpp" -#include "MatchImageAnalyzer.h" -#include "OcrImageAnalyzer.h" -#include "Resource.h" -#include "ProcessTask.h" - -int asst::InfrastAbstractTask::m_face_hash_thres = 0; -int asst::InfrastAbstractTask::m_name_hash_thres = 0; - -asst::InfrastAbstractTask::InfrastAbstractTask(AsstCallback callback, void* callback_arg) - : AbstractTask(callback, callback_arg) -{ - if (m_face_hash_thres == 0) { - m_face_hash_thres = std::dynamic_pointer_cast( - Task.get("InfrastOperFaceHash"))->templ_threshold; - } - if (m_name_hash_thres == 0) { - m_name_hash_thres = std::dynamic_pointer_cast( - Task.get("InfrastOperNameHash"))->templ_threshold; - } -} - -void asst::InfrastAbstractTask::set_work_mode(infrast::WorkMode work_mode) noexcept -{ - m_work_mode = work_mode; - switch (work_mode) { - case infrast::WorkMode::Gentle: - m_work_mode_name = "Gentle"; - break; - case infrast::WorkMode::Aggressive: - m_work_mode_name = "Aggressive"; - break; - case infrast::WorkMode::Extreme: - m_work_mode_name = "Extreme"; - break; - default: - m_work_mode_name.clear(); - break; - } -} - -void asst::InfrastAbstractTask::set_mood_threshold(double mood_thres) noexcept -{ - m_mood_threshold = mood_thres; -} - -bool asst::InfrastAbstractTask::on_run_fails() -{ - LogTraceFunction; - - ProcessTask return_task(*this, { "InfrastBegin" }); - - return return_task.run(); -} - -bool asst::InfrastAbstractTask::enter_facility(const std::string& facility, int index) -{ - LogTraceFunction; - json::value enter_json = json::object{ - { "facility", facility }, - { "index", index } - }; - m_callback(AsstMsg::EnterFacility, enter_json, m_callback_arg); - - const auto image = Ctrler.get_image(); - - InfrastFacilityImageAnalyzer analyzer(image); - analyzer.set_to_be_analyzed({ facility }); - if (!analyzer.analyze()) { - Log.trace("result is empty"); - return false; - } - Rect rect = analyzer.get_rect(facility, index); - if (rect.empty()) { - Log.trace("facility index is out of range"); - return false; - } - - Ctrler.click(rect); - - const auto enter_task_ptr = Task.get("InfrastEnterFacility"); - sleep(enter_task_ptr->rear_delay); - - return true; -} - -bool asst::InfrastAbstractTask::enter_oper_list_page() -{ - LogTraceFunction; - - auto image = Ctrler.get_image(); - - // 识别左边的“进驻”按钮 - const auto enter_task_ptr = std::dynamic_pointer_cast( - Task.get("InfrastEnterOperList")); - OcrImageAnalyzer enter_analyzer(image); - enter_analyzer.set_task_info(*enter_task_ptr); - - // 如果没找到,说明“进驻信息”这个按钮没有被点开,那就点开它 - if (!enter_analyzer.analyze()) { - Log.trace("ready to analyze the stationed info button"); - OcrImageAnalyzer station_analyzer(image); - - const auto stationedinfo_task_ptr = std::dynamic_pointer_cast( - Task.get("InfrastStationedInfo")); - station_analyzer.set_task_info(*stationedinfo_task_ptr); - if (station_analyzer.analyze()) { - Log.trace("the stationed info button found"); - Ctrler.click(station_analyzer.get_result().front().rect); - sleep(stationedinfo_task_ptr->rear_delay); - // 点开了按钮之后,再识别一次右边的 - image = Ctrler.get_image(); - enter_analyzer.set_image(image); - if (!enter_analyzer.analyze()) { - Log.error("no enterlist button"); - return false; - } - } - else { - Log.error("no stationed info button"); - return false; - } - } - Log.trace("ready to click the enterlist button"); - Ctrler.click(enter_analyzer.get_result().front().rect); - sleep(enter_task_ptr->rear_delay); - - return true; -} - -void asst::InfrastAbstractTask::async_swipe_of_operlist(bool reverse) -{ - LogTraceFunction; - static Rect begin_rect = Task.get("InfrastOperListSwipeBegin")->specific_rect; - static Rect end_rect = Task.get("InfrastOperListSwipeEnd")->specific_rect; - static int duration = Task.get("InfrastOperListSwipeBegin")->pre_delay; - - if (!reverse) { - m_last_swipe_id = Ctrler.swipe(begin_rect, end_rect, duration, false, 0, true); - } - else { - m_last_swipe_id = Ctrler.swipe(end_rect, begin_rect, duration, false, 0, true); - } -} - -void asst::InfrastAbstractTask::await_swipe() -{ - LogTraceFunction; - static int extra_delay = Task.get("InfrastOperListSwipeBegin")->rear_delay; - - Ctrler.wait(m_last_swipe_id); - sleep(extra_delay); -} - -bool asst::InfrastAbstractTask::click_bottomleft_tab() -{ - LogTraceFunction; - const auto task_ptr = Task.get("InfrastBottomLeftTab"); - Ctrler.click(task_ptr->specific_rect); - sleep(task_ptr->rear_delay); - return true; -} - -bool asst::InfrastAbstractTask::click_clear_button() -{ - LogTraceFunction; - const auto task_ptr = Task.get("InfrastClearButton"); - Ctrler.click(task_ptr->specific_rect); - sleep(task_ptr->rear_delay); - return true; -} - -bool asst::InfrastAbstractTask::click_confirm_button() -{ - LogTraceFunction; - const auto task_ptr = std::dynamic_pointer_cast( - Task.get("InfrastConfirmButton")); - Ctrler.click(task_ptr->specific_rect); - sleep(task_ptr->rear_delay); - - // 识别“正在提交反馈至神经”,如果网不好一直确认不了,就多等一会 - OcrImageAnalyzer analyzer; - analyzer.set_task_info(*task_ptr); - for (int i = 0; i != m_retry_times; ++i) { - if (need_exit()) { - return false; - } - const auto image = Ctrler.get_image(); - analyzer.set_image(image); - if (!analyzer.analyze()) { - sleep(task_ptr->rear_delay); - return true; - } - sleep(task_ptr->rear_delay); - } - return false; -} - -void asst::InfrastAbstractTask::swipe_of_operlist(bool reverse) -{ - async_swipe_of_operlist(reverse); - await_swipe(); -} - -void asst::InfrastAbstractTask::swipe_to_the_left_of_operlist(int loop_times) -{ - LogTraceFunction; - static Rect begin_rect = Task.get("InfrastOperListSwipeToTheLeftBegin")->specific_rect; - static Rect end_rect = Task.get("InfrastOperListSwipeToTheLeftEnd")->specific_rect; - static int duration = Task.get("InfrastOperListSwipeToTheLeftBegin")->pre_delay; - static int extra_delay = Task.get("InfrastOperListSwipeToTheLeftBegin")->rear_delay; - static int cfg_loop_times = Task.get("InfrastOperListSwipeToTheLeftBegin")->max_times; - - for (int i = 0; i != cfg_loop_times * loop_times; ++i) { - if (need_exit()) { - return; - } - Ctrler.swipe(end_rect, begin_rect, duration, true, 0, false); - } - sleep(extra_delay); -} - -void asst::InfrastAbstractTask::swipe_to_the_left_of_main_ui() -{ - LogTraceFunction; - static Rect begin_rect = Task.get("InfrastOperListSwipeToTheLeftBegin")->specific_rect; - static Rect end_rect = Task.get("InfrastOperListSwipeToTheLeftEnd")->specific_rect; - static int duration = Task.get("InfrastOperListSwipeToTheLeftBegin")->pre_delay; - static int extra_delay = Task.get("InfrastOperListSwipeToTheLeftBegin")->rear_delay; - - Ctrler.swipe(end_rect, begin_rect, duration, true, extra_delay, false); -} - -void asst::InfrastAbstractTask::swipe_to_the_right_of_main_ui() -{ - LogTraceFunction; - static Rect begin_rect = Task.get("InfrastOperListSwipeToTheLeftBegin")->specific_rect; - static Rect end_rect = Task.get("InfrastOperListSwipeToTheLeftEnd")->specific_rect; - static int duration = Task.get("InfrastOperListSwipeToTheLeftBegin")->pre_delay; - static int extra_delay = Task.get("InfrastOperListSwipeToTheLeftBegin")->rear_delay; - - Ctrler.swipe(begin_rect, end_rect, duration, true, extra_delay, false); -} \ No newline at end of file +#include "InfrastAbstractTask.h" + +#include "AsstMsg.h" +#include "Controller.h" +#include "InfrastFacilityImageAnalyzer.h" +#include "Logger.hpp" +#include "MatchImageAnalyzer.h" +#include "OcrImageAnalyzer.h" +#include "Resource.h" +#include "ProcessTask.h" + +int asst::InfrastAbstractTask::m_face_hash_thres = 0; +int asst::InfrastAbstractTask::m_name_hash_thres = 0; + +asst::InfrastAbstractTask::InfrastAbstractTask(AsstCallback callback, void* callback_arg) + : AbstractTask(callback, callback_arg) +{ + if (m_face_hash_thres == 0) { + m_face_hash_thres = std::dynamic_pointer_cast( + Task.get("InfrastOperFaceHash"))->templ_threshold; + } + if (m_name_hash_thres == 0) { + m_name_hash_thres = std::dynamic_pointer_cast( + Task.get("InfrastOperNameHash"))->templ_threshold; + } +} + +void asst::InfrastAbstractTask::set_work_mode(infrast::WorkMode work_mode) noexcept +{ + m_work_mode = work_mode; + switch (work_mode) { + case infrast::WorkMode::Gentle: + m_work_mode_name = "Gentle"; + break; + case infrast::WorkMode::Aggressive: + m_work_mode_name = "Aggressive"; + break; + case infrast::WorkMode::Extreme: + m_work_mode_name = "Extreme"; + break; + default: + m_work_mode_name.clear(); + break; + } +} + +void asst::InfrastAbstractTask::set_mood_threshold(double mood_thres) noexcept +{ + m_mood_threshold = mood_thres; +} + +bool asst::InfrastAbstractTask::on_run_fails() +{ + LogTraceFunction; + + ProcessTask return_task(*this, { "InfrastBegin" }); + + return return_task.run(); +} + +bool asst::InfrastAbstractTask::enter_facility(const std::string& facility, int index) +{ + LogTraceFunction; + json::value enter_json = json::object{ + { "facility", facility }, + { "index", index } + }; + m_callback(AsstMsg::EnterFacility, enter_json, m_callback_arg); + + const auto image = Ctrler.get_image(); + + InfrastFacilityImageAnalyzer analyzer(image); + analyzer.set_to_be_analyzed({ facility }); + if (!analyzer.analyze()) { + Log.trace("result is empty"); + return false; + } + Rect rect = analyzer.get_rect(facility, index); + if (rect.empty()) { + Log.trace("facility index is out of range"); + return false; + } + + Ctrler.click(rect); + + const auto enter_task_ptr = Task.get("InfrastEnterFacility"); + sleep(enter_task_ptr->rear_delay); + + return true; +} + +bool asst::InfrastAbstractTask::enter_oper_list_page() +{ + LogTraceFunction; + + auto image = Ctrler.get_image(); + + // 识别左边的“进驻”按钮 + const auto enter_task_ptr = std::dynamic_pointer_cast( + Task.get("InfrastEnterOperList")); + OcrImageAnalyzer enter_analyzer(image); + enter_analyzer.set_task_info(*enter_task_ptr); + + // 如果没找到,说明“进驻信息”这个按钮没有被点开,那就点开它 + if (!enter_analyzer.analyze()) { + Log.trace("ready to analyze the stationed info button"); + OcrImageAnalyzer station_analyzer(image); + + const auto stationedinfo_task_ptr = std::dynamic_pointer_cast( + Task.get("InfrastStationedInfo")); + station_analyzer.set_task_info(*stationedinfo_task_ptr); + if (station_analyzer.analyze()) { + Log.trace("the stationed info button found"); + Ctrler.click(station_analyzer.get_result().front().rect); + sleep(stationedinfo_task_ptr->rear_delay); + // 点开了按钮之后,再识别一次右边的 + image = Ctrler.get_image(); + enter_analyzer.set_image(image); + if (!enter_analyzer.analyze()) { + Log.error("no enterlist button"); + return false; + } + } + else { + Log.error("no stationed info button"); + return false; + } + } + Log.trace("ready to click the enterlist button"); + Ctrler.click(enter_analyzer.get_result().front().rect); + sleep(enter_task_ptr->rear_delay); + + return true; +} + +void asst::InfrastAbstractTask::async_swipe_of_operlist(bool reverse) +{ + LogTraceFunction; + static Rect begin_rect = Task.get("InfrastOperListSwipeBegin")->specific_rect; + static Rect end_rect = Task.get("InfrastOperListSwipeEnd")->specific_rect; + static int duration = Task.get("InfrastOperListSwipeBegin")->pre_delay; + + if (!reverse) { + m_last_swipe_id = Ctrler.swipe(begin_rect, end_rect, duration, false, 0, true); + } + else { + m_last_swipe_id = Ctrler.swipe(end_rect, begin_rect, duration, false, 0, true); + } +} + +void asst::InfrastAbstractTask::await_swipe() +{ + LogTraceFunction; + static int extra_delay = Task.get("InfrastOperListSwipeBegin")->rear_delay; + + Ctrler.wait(m_last_swipe_id); + sleep(extra_delay); +} + +bool asst::InfrastAbstractTask::click_bottomleft_tab() +{ + LogTraceFunction; + const auto task_ptr = Task.get("InfrastBottomLeftTab"); + Ctrler.click(task_ptr->specific_rect); + sleep(task_ptr->rear_delay); + return true; +} + +bool asst::InfrastAbstractTask::click_clear_button() +{ + LogTraceFunction; + const auto task_ptr = Task.get("InfrastClearButton"); + Ctrler.click(task_ptr->specific_rect); + sleep(task_ptr->rear_delay); + return true; +} + +bool asst::InfrastAbstractTask::click_confirm_button() +{ + LogTraceFunction; + const auto task_ptr = std::dynamic_pointer_cast( + Task.get("InfrastConfirmButton")); + Ctrler.click(task_ptr->specific_rect); + sleep(task_ptr->rear_delay); + + // 识别“正在提交反馈至神经”,如果网不好一直确认不了,就多等一会 + OcrImageAnalyzer analyzer; + analyzer.set_task_info(*task_ptr); + for (int i = 0; i != m_retry_times; ++i) { + if (need_exit()) { + return false; + } + const auto image = Ctrler.get_image(); + analyzer.set_image(image); + if (!analyzer.analyze()) { + sleep(task_ptr->rear_delay); + return true; + } + sleep(task_ptr->rear_delay); + } + return false; +} + +void asst::InfrastAbstractTask::swipe_of_operlist(bool reverse) +{ + async_swipe_of_operlist(reverse); + await_swipe(); +} + +void asst::InfrastAbstractTask::swipe_to_the_left_of_operlist(int loop_times) +{ + LogTraceFunction; + static Rect begin_rect = Task.get("InfrastOperListSwipeToTheLeftBegin")->specific_rect; + static Rect end_rect = Task.get("InfrastOperListSwipeToTheLeftEnd")->specific_rect; + static int duration = Task.get("InfrastOperListSwipeToTheLeftBegin")->pre_delay; + static int extra_delay = Task.get("InfrastOperListSwipeToTheLeftBegin")->rear_delay; + static int cfg_loop_times = Task.get("InfrastOperListSwipeToTheLeftBegin")->max_times; + + for (int i = 0; i != cfg_loop_times * loop_times; ++i) { + if (need_exit()) { + return; + } + Ctrler.swipe(end_rect, begin_rect, duration, true, 0, false); + } + sleep(extra_delay); +} + +void asst::InfrastAbstractTask::swipe_to_the_left_of_main_ui() +{ + LogTraceFunction; + static Rect begin_rect = Task.get("InfrastOperListSwipeToTheLeftBegin")->specific_rect; + static Rect end_rect = Task.get("InfrastOperListSwipeToTheLeftEnd")->specific_rect; + static int duration = Task.get("InfrastOperListSwipeToTheLeftBegin")->pre_delay; + static int extra_delay = Task.get("InfrastOperListSwipeToTheLeftBegin")->rear_delay; + + Ctrler.swipe(end_rect, begin_rect, duration, true, extra_delay, false); +} + +void asst::InfrastAbstractTask::swipe_to_the_right_of_main_ui() +{ + LogTraceFunction; + static Rect begin_rect = Task.get("InfrastOperListSwipeToTheLeftBegin")->specific_rect; + static Rect end_rect = Task.get("InfrastOperListSwipeToTheLeftEnd")->specific_rect; + static int duration = Task.get("InfrastOperListSwipeToTheLeftBegin")->pre_delay; + static int extra_delay = Task.get("InfrastOperListSwipeToTheLeftBegin")->rear_delay; + + Ctrler.swipe(begin_rect, end_rect, duration, true, extra_delay, false); +} diff --git a/src/MeoAssistant/InfrastClueImageAnalyzer.cpp b/src/MeoAssistant/InfrastClueImageAnalyzer.cpp index 98e42fd288..ad862055bf 100644 --- a/src/MeoAssistant/InfrastClueImageAnalyzer.cpp +++ b/src/MeoAssistant/InfrastClueImageAnalyzer.cpp @@ -35,4 +35,4 @@ bool asst::InfrastClueImageAnalyzer::clue_analyze() { // todo return false; -} \ No newline at end of file +} diff --git a/src/MeoAssistant/InfrastClueVacancyImageAnalyzer.cpp b/src/MeoAssistant/InfrastClueVacancyImageAnalyzer.cpp index f10d007c5e..11ab2663ad 100644 --- a/src/MeoAssistant/InfrastClueVacancyImageAnalyzer.cpp +++ b/src/MeoAssistant/InfrastClueVacancyImageAnalyzer.cpp @@ -28,4 +28,4 @@ bool asst::InfrastClueVacancyImageAnalyzer::analyze() } return !m_clue_vacancy.empty(); -} \ No newline at end of file +} diff --git a/src/MeoAssistant/InfrastConfiger.cpp b/src/MeoAssistant/InfrastConfiger.cpp index 261a7f226e..14816b9d04 100644 --- a/src/MeoAssistant/InfrastConfiger.cpp +++ b/src/MeoAssistant/InfrastConfiger.cpp @@ -209,4 +209,4 @@ bool asst::InfrastConfiger::parse(const json::value& json) m_facilities_info.emplace(facility_name, std::move(fac_info)); } return true; -} \ No newline at end of file +} diff --git a/src/MeoAssistant/InfrastDormTask.cpp b/src/MeoAssistant/InfrastDormTask.cpp index 3fe71d2355..dd1996690c 100644 --- a/src/MeoAssistant/InfrastDormTask.cpp +++ b/src/MeoAssistant/InfrastDormTask.cpp @@ -1,141 +1,141 @@ -#include "InfrastDormTask.h" - -#include "Controller.h" -#include "InfrastOperImageAnalyzer.h" -#include "Logger.hpp" -#include "MatchImageAnalyzer.h" -#include "OcrImageAnalyzer.h" -#include "Resource.h" - -const std::string asst::InfrastDormTask::FacilityName = "Dorm"; - -bool asst::InfrastDormTask::_run() -{ - json::value task_start_json = json::object{ - { "task_type", "InfrastDormTask" }, - { "task_chain", m_task_chain } - }; - m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); - - for (; m_cur_dorm_index < m_max_num_of_dorm; ++m_cur_dorm_index) { - if (need_exit()) { - return false; - } - // 进不去说明设施数量不够 - if (!enter_facility(FacilityName, m_cur_dorm_index)) { - break; - } - if (!enter_oper_list_page()) { - return false; - } - click_clear_button(); - - opers_choose(); - - click_confirm_button(); - click_return_button(); - - if (m_all_finished) { - break; - } - } - return true; -} - -bool asst::InfrastDormTask::opers_choose() -{ - int num_of_selected = 0; - while (num_of_selected < MaxNumOfOpers) { - if (need_exit()) { - return false; - } - const auto image = Ctrler.get_image(); - InfrastOperImageAnalyzer oper_analyzer(image); - const int without_skill = InfrastOperImageAnalyzer::All ^ InfrastOperImageAnalyzer::Skill; - oper_analyzer.set_to_be_calced(without_skill); - if (!oper_analyzer.analyze()) { - Log.error("mood analyze faild!"); - return false; - } - oper_analyzer.sort_by_mood(); - const auto& oper_result = oper_analyzer.get_result(); - - int num_of_resting = 0; - for (const auto& oper : oper_result) { - if (need_exit()) { - return false; - } - if (num_of_selected >= MaxNumOfOpers) { - Log.trace("num_of_selected:", num_of_selected, ", just break"); - break; - } - switch (oper.smiley.type) { - case infrast::SmileyType::Rest: - // 如果当前页面休息完成的人数超过5个,说明已经已经把所有心情不满的滑过一遍、没有更多的了 - if (++num_of_resting > MaxNumOfOpers) { - Log.trace("num_of_resting:", num_of_resting, ", dorm finished"); - m_all_finished = true; - return true; - } - break; - case infrast::SmileyType::Work: - case infrast::SmileyType::Distract: - // 干员没有被选择的情况下,且不在工作,就进驻宿舍 - if (oper.selected == false && oper.doing != infrast::Doing::Working) { - Ctrler.click(oper.rect); - if (++num_of_selected >= MaxNumOfOpers) { - Log.trace("num_of_selected:", num_of_selected, ", just break"); - break; - } - } - break; - default: - break; - } - } - if (num_of_selected >= MaxNumOfOpers) { - Log.trace("num_of_selected:", num_of_selected, ", just break"); - break; - } - swipe_of_operlist(); - } - return true; -} - -//bool asst::InfrastDormTask::click_confirm_button() -//{ -// LogTraceFunction; -// -// const auto task_ptr = std::dynamic_pointer_cast( -// Task.get("InfrastConfirmButton")); -// Ctrler.click(task_ptr->specific_rect); -// sleep(task_ptr->rear_delay); -// -// // 宿舍在把正在工作的干员换下来的时候,会有个二次确认的按钮 -// const auto image = Ctrler.get_image(); -// MatchImageAnalyzer cfm_analyzer(image); -// const auto sec_cfm_task_ptr = std::dynamic_pointer_cast( -// Task.get("InfrastDormConfirmButton")); -// cfm_analyzer.set_task_info(*sec_cfm_task_ptr); -// if (cfm_analyzer.analyze()) { -// Ctrler.click(cfm_analyzer.get_result().rect); -// sleep(sec_cfm_task_ptr->rear_delay); -// } -// -// // 识别“正在提交反馈至神经”,如果网不好一直确认不了,就多等一会 -// OcrImageAnalyzer analyzer; -// analyzer.set_task_info(*task_ptr); -// for (int i = 0; i != m_retry_times; ++i) { -// if (need_exit()) { -// return false; -// } -// const auto image = Ctrler.get_image(); -// analyzer.set_image(image); -// if (!analyzer.analyze()) { -// sleep(sec_cfm_task_ptr->rear_delay); -// return true; -// } -// sleep(sec_cfm_task_ptr->rear_delay); -// } -// return false; -//} \ No newline at end of file +#include "InfrastDormTask.h" + +#include "Controller.h" +#include "InfrastOperImageAnalyzer.h" +#include "Logger.hpp" +#include "MatchImageAnalyzer.h" +#include "OcrImageAnalyzer.h" +#include "Resource.h" + +const std::string asst::InfrastDormTask::FacilityName = "Dorm"; + +bool asst::InfrastDormTask::_run() +{ + json::value task_start_json = json::object{ + { "task_type", "InfrastDormTask" }, + { "task_chain", m_task_chain } + }; + m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); + + for (; m_cur_dorm_index < m_max_num_of_dorm; ++m_cur_dorm_index) { + if (need_exit()) { + return false; + } + // 进不去说明设施数量不够 + if (!enter_facility(FacilityName, m_cur_dorm_index)) { + break; + } + if (!enter_oper_list_page()) { + return false; + } + click_clear_button(); + + opers_choose(); + + click_confirm_button(); + click_return_button(); + + if (m_all_finished) { + break; + } + } + return true; +} + +bool asst::InfrastDormTask::opers_choose() +{ + int num_of_selected = 0; + while (num_of_selected < MaxNumOfOpers) { + if (need_exit()) { + return false; + } + const auto image = Ctrler.get_image(); + InfrastOperImageAnalyzer oper_analyzer(image); + const int without_skill = InfrastOperImageAnalyzer::All ^ InfrastOperImageAnalyzer::Skill; + oper_analyzer.set_to_be_calced(without_skill); + if (!oper_analyzer.analyze()) { + Log.error("mood analyze faild!"); + return false; + } + oper_analyzer.sort_by_mood(); + const auto& oper_result = oper_analyzer.get_result(); + + int num_of_resting = 0; + for (const auto& oper : oper_result) { + if (need_exit()) { + return false; + } + if (num_of_selected >= MaxNumOfOpers) { + Log.trace("num_of_selected:", num_of_selected, ", just break"); + break; + } + switch (oper.smiley.type) { + case infrast::SmileyType::Rest: + // 如果当前页面休息完成的人数超过5个,说明已经已经把所有心情不满的滑过一遍、没有更多的了 + if (++num_of_resting > MaxNumOfOpers) { + Log.trace("num_of_resting:", num_of_resting, ", dorm finished"); + m_all_finished = true; + return true; + } + break; + case infrast::SmileyType::Work: + case infrast::SmileyType::Distract: + // 干员没有被选择的情况下,且不在工作,就进驻宿舍 + if (oper.selected == false && oper.doing != infrast::Doing::Working) { + Ctrler.click(oper.rect); + if (++num_of_selected >= MaxNumOfOpers) { + Log.trace("num_of_selected:", num_of_selected, ", just break"); + break; + } + } + break; + default: + break; + } + } + if (num_of_selected >= MaxNumOfOpers) { + Log.trace("num_of_selected:", num_of_selected, ", just break"); + break; + } + swipe_of_operlist(); + } + return true; +} + +//bool asst::InfrastDormTask::click_confirm_button() +//{ +// LogTraceFunction; +// +// const auto task_ptr = std::dynamic_pointer_cast( +// Task.get("InfrastConfirmButton")); +// Ctrler.click(task_ptr->specific_rect); +// sleep(task_ptr->rear_delay); +// +// // 宿舍在把正在工作的干员换下来的时候,会有个二次确认的按钮 +// const auto image = Ctrler.get_image(); +// MatchImageAnalyzer cfm_analyzer(image); +// const auto sec_cfm_task_ptr = std::dynamic_pointer_cast( +// Task.get("InfrastDormConfirmButton")); +// cfm_analyzer.set_task_info(*sec_cfm_task_ptr); +// if (cfm_analyzer.analyze()) { +// Ctrler.click(cfm_analyzer.get_result().rect); +// sleep(sec_cfm_task_ptr->rear_delay); +// } +// +// // 识别“正在提交反馈至神经”,如果网不好一直确认不了,就多等一会 +// OcrImageAnalyzer analyzer; +// analyzer.set_task_info(*task_ptr); +// for (int i = 0; i != m_retry_times; ++i) { +// if (need_exit()) { +// return false; +// } +// const auto image = Ctrler.get_image(); +// analyzer.set_image(image); +// if (!analyzer.analyze()) { +// sleep(sec_cfm_task_ptr->rear_delay); +// return true; +// } +// sleep(sec_cfm_task_ptr->rear_delay); +// } +// return false; +//} diff --git a/src/MeoAssistant/InfrastDormTask.h b/src/MeoAssistant/InfrastDormTask.h index 7883e6e86b..fa79adfc88 100644 --- a/src/MeoAssistant/InfrastDormTask.h +++ b/src/MeoAssistant/InfrastDormTask.h @@ -13,12 +13,12 @@ namespace asst const static int MaxNumOfOpers = 5; private: - virtual bool _run() override; + virtual bool _run() override; bool opers_choose(); //virtual bool click_confirm_button() override; int m_cur_dorm_index = 0; - int m_max_num_of_dorm = 4; + int m_max_num_of_dorm = 4; int m_all_finished = false; }; } diff --git a/src/MeoAssistant/InfrastInfoTask.cpp b/src/MeoAssistant/InfrastInfoTask.cpp index dc04234a2a..ecdf8828fa 100644 --- a/src/MeoAssistant/InfrastInfoTask.cpp +++ b/src/MeoAssistant/InfrastInfoTask.cpp @@ -1,33 +1,33 @@ -#include "InfrastInfoTask.h" - -#include "Controller.h" -#include "InfrastFacilityImageAnalyzer.h" -#include "Logger.hpp" -#include "Resource.h" -#include "RuntimeStatus.h" - -bool asst::InfrastInfoTask::_run() -{ - json::value task_start_json = json::object{ - { "task_type", "InfrastInfoTask" }, - { "task_chain", m_task_chain } - }; - m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); - - swipe_to_the_left_of_main_ui(); - const auto image = Ctrler.get_image(); - - InfrastFacilityImageAnalyzer analyzer(image); - analyzer.set_to_be_analyzed({ "Mfg", "Trade", "Power", "Dorm" }); - if (!analyzer.analyze()) { - return false; - } - for (auto&& [name, res] : analyzer.get_result()) { - std::string key = "NumOf" + name; - //int size = static_cast(res.size()); - Status.set(key, res.size()); - Log.trace("InfrastInfoTask | ", key, res.size()); - } - - return true; -} \ No newline at end of file +#include "InfrastInfoTask.h" + +#include "Controller.h" +#include "InfrastFacilityImageAnalyzer.h" +#include "Logger.hpp" +#include "Resource.h" +#include "RuntimeStatus.h" + +bool asst::InfrastInfoTask::_run() +{ + json::value task_start_json = json::object{ + { "task_type", "InfrastInfoTask" }, + { "task_chain", m_task_chain } + }; + m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); + + swipe_to_the_left_of_main_ui(); + const auto image = Ctrler.get_image(); + + InfrastFacilityImageAnalyzer analyzer(image); + analyzer.set_to_be_analyzed({ "Mfg", "Trade", "Power", "Dorm" }); + if (!analyzer.analyze()) { + return false; + } + for (auto&& [name, res] : analyzer.get_result()) { + std::string key = "NumOf" + name; + //int size = static_cast(res.size()); + Status.set(key, res.size()); + Log.trace("InfrastInfoTask | ", key, res.size()); + } + + return true; +} diff --git a/src/MeoAssistant/InfrastProductionTask.cpp b/src/MeoAssistant/InfrastProductionTask.cpp index 88578c0da0..33f727cb20 100644 --- a/src/MeoAssistant/InfrastProductionTask.cpp +++ b/src/MeoAssistant/InfrastProductionTask.cpp @@ -1,622 +1,622 @@ -#include "InfrastProductionTask.h" - -#include - -#include - -#include "AsstUtils.hpp" -#include "Controller.h" -#include "InfrastOperImageAnalyzer.h" -#include "Logger.hpp" -#include "MatchImageAnalyzer.h" -#include "MultiMatchImageAnalyzer.h" -#include "Resource.h" -#include "RuntimeStatus.h" -#include "ProcessTask.h" - -bool asst::InfrastProductionTask::shift_facility_list() -{ - LogTraceFunction; - if (!facility_list_detect()) { - return false; - } - if (need_exit()) { - return false; - } - const auto tab_task_ptr = std::dynamic_pointer_cast( - Task.get("InfrastFacilityListTab" + m_facility)); - MatchImageAnalyzer add_analyzer; - const auto add_task_ptr = std::dynamic_pointer_cast( - Task.get("InfrastAddOperator" + m_facility + m_work_mode_name)); - add_analyzer.set_task_info(*add_task_ptr); - - const auto locked_task_ptr = std::dynamic_pointer_cast( - Task.get("InfrastOperLocked" + m_facility)); - MultiMatchImageAnalyzer locked_analyzer; - locked_analyzer.set_task_info(*locked_task_ptr); - - int index = 0; - for (const Rect& tab : m_facility_list_tabs) { - if (need_exit()) { - return false; - } - if (index != 0) { - json::value enter_json = json::object{ - { "facility", m_facility }, - { "index", index } - }; - m_callback(AsstMsg::EnterFacility, enter_json, m_callback_arg); - } - - ++index; - - Ctrler.click(tab); - sleep(tab_task_ptr->rear_delay); - - /* 识别当前制造/贸易站有没有添加干员按钮,没有就不换班 */ - const auto image = Ctrler.get_image(); - add_analyzer.set_image(image); - if (!add_analyzer.analyze()) { - Log.info("no add button, just continue"); - continue; - } - auto& rect = add_analyzer.get_result().rect; - Rect add_button = rect; - auto& rect_move = add_task_ptr->rect_move; - if (!rect_move.empty()) { - add_button.x += rect_move.x; - add_button.y += rect_move.y; - add_button.width = rect_move.width; - add_button.height = rect_move.height; - } - - /* 识别当前正在造什么 */ - MatchImageAnalyzer product_analyzer(image); - auto& all_products = Resrc.infrast().get_facility_info(m_facility).products; - std::string cur_product = all_products.at(0); - double max_score = 0; - for (const std::string& product : all_products) { - const static std::string prefix = "InfrastFlag"; - const auto task_ptr = std::dynamic_pointer_cast( - Task.get(prefix + product)); - product_analyzer.set_task_info(*task_ptr); - if (product_analyzer.analyze()) { - double score = product_analyzer.get_result().score; - if (score > max_score) { - max_score = score; - cur_product = product; - } - } - } - set_product(cur_product); - Log.info("cur product", cur_product); - - locked_analyzer.set_image(image); - if (locked_analyzer.analyze()) { - m_cur_num_of_lokced_opers = locked_analyzer.get_result().size(); - } - else { - m_cur_num_of_lokced_opers = 0; - } - - /* 进入干员选择页面 */ - Ctrler.click(add_button); - sleep(add_task_ptr->rear_delay); - - for (int i = 0; i <= OperSelectRetryTimes; ++i) { - if (need_exit()) { - return false; - } - click_clear_button(); - - if (m_all_available_opers.empty()) { - if (!opers_detect_with_swipe()) { - return false; - } - swipe_to_the_left_of_operlist(2); - } - else { - opers_detect(); - } - optimal_calc(); - bool ret = opers_choose(); - if (!ret) { - m_all_available_opers.clear(); - swipe_to_the_left_of_operlist(2); - continue; - } - break; - } - click_confirm_button(); - - // 使用无人机 - if (cur_product == m_uses_of_drones) { - if (use_drone()) { - m_uses_of_drones = "_Used"; - } - } - } - return true; -} - -bool asst::InfrastProductionTask::opers_detect_with_swipe() -{ - LogTraceFunction; - m_all_available_opers.clear(); - - int first_number = 0; - while (true) { - if (need_exit()) { - return false; - } - size_t num = opers_detect(); - Log.trace("opers_detect return", num); - - // 无论如何也不会没有干员的,除非前面的操作哪里出错了,没进到干员选择的页面。那也就没必要继续下去了 - if (num == 0) { - return false; - } - - // 这里本来是判断不相等就可以退出循环。 - // 但是有时候滑动会把一个干员挡住一半,一个页面完整的干员真的只有10个,所以加个2的差值 - if (max_num_of_opers_per_page - num > 2) { - break; - } - - // 异步在最后会多滑动一下,耽误的时间还不如用同步 - swipe_of_operlist(); - } - - if (!m_all_available_opers.empty()) { - return true; - } - return false; -} - -size_t asst::InfrastProductionTask::opers_detect() -{ - LogTraceFunction; - const auto image = Ctrler.get_image(); - - InfrastOperImageAnalyzer oper_analyzer(image); - oper_analyzer.set_facility(m_facility); - - if (!oper_analyzer.analyze()) { - return 0; - } - const auto& cur_all_opers = oper_analyzer.get_result(); - max_num_of_opers_per_page = (std::max)(max_num_of_opers_per_page, cur_all_opers.size()); - - int cur_available_num = cur_all_opers.size(); - for (const auto& cur_oper : cur_all_opers) { - if (cur_oper.skills.empty()) { - --cur_available_num; - continue; - } - // 心情过低的干员则不可用 - if (cur_oper.mood_ratio < m_mood_threshold) { - //--cur_available_num; - continue; - } - auto find_iter = std::find_if( - m_all_available_opers.cbegin(), m_all_available_opers.cend(), - [&cur_oper](const infrast::Oper& oper) -> bool { - // 有可能是同一个干员,比一下hash - int dist = utils::hamming(cur_oper.face_hash, oper.face_hash); -#ifdef LOG_TRACE - Log.trace("opers_detect hash dist |", dist); -#endif - return dist < m_face_hash_thres; - }); - // 如果两个的hash距离过小,则认为是同一个干员,不进行插入 - if (find_iter != m_all_available_opers.cend()) { - continue; - } - m_all_available_opers.emplace_back(cur_oper); - } - return cur_available_num; -} - -bool asst::InfrastProductionTask::optimal_calc() -{ - LogTraceFunction; - auto& facility_info = Resrc.infrast().get_facility_info(m_facility); - int cur_max_num_of_opers = facility_info.max_num_of_opers - m_cur_num_of_lokced_opers; - - std::vector all_avaliable_combs; - all_avaliable_combs.reserve(m_all_available_opers.size()); - for (auto&& oper : m_all_available_opers) { - auto comb = efficient_regex_calc(oper.skills); - comb.name_hash = oper.name_hash; - all_avaliable_combs.emplace_back(std::move(comb)); - } - - // 先把单个的技能按效率排个序,取效率最高的几个 - std::vector optimal_combs; - optimal_combs.reserve(cur_max_num_of_opers); - double max_efficient = 0; - std::sort(all_avaliable_combs.begin(), all_avaliable_combs.end(), - [&](const infrast::SkillsComb& lhs, const infrast::SkillsComb& rhs) -> bool { - return lhs.efficient.at(m_product) > rhs.efficient.at(m_product); - }); - - for (const auto& comb : all_avaliable_combs) { - std::string skill_str; - for (const auto& skill : comb.skills) { - skill_str += skill.id + " "; - } - Log.trace(skill_str, comb.efficient.at(m_product)); - } - - std::unordered_map skills_num; - for (int i = 0; i != m_all_available_opers.size(); ++i) { - auto comb = all_avaliable_combs.at(i); - - bool out_of_num = false; - for (auto&& skill : comb.skills) { - if (skills_num[skill.id] >= skill.max_num) { - out_of_num = true; - break; - } - } - if (out_of_num) { - continue; - } - - optimal_combs.emplace_back(comb); - max_efficient += all_avaliable_combs.at(i).efficient.at(m_product); - - for (auto&& skill : comb.skills) { - ++skills_num[skill.id]; - } - - if (optimal_combs.size() >= cur_max_num_of_opers) { - break; - } - } - - { - std::string log_str = "[ "; - for (const auto& comb : optimal_combs) { - log_str += comb.desc.empty() ? comb.skills.begin()->names.front() : comb.desc; - log_str += "; "; - } - log_str += "]"; - Log.trace("Single comb efficient", max_efficient, " , skills:", log_str); - } - - // 如果有被锁住的干员,说明当前基建没升满级,组合就不启用 - if (m_cur_num_of_lokced_opers != 0) { - m_optimal_combs = std::move(optimal_combs); - return true; - } - - // 遍历所有组合,找到效率最高的 - auto& all_group = Resrc.infrast().get_skills_group(m_facility); - for (const infrast::SkillsGroup& group : all_group) { - LogTraceScope(group.desc); - auto cur_available_opers = all_avaliable_combs; - bool group_unavailable = false; - std::vector cur_combs; - cur_combs.reserve(cur_max_num_of_opers); - double cur_efficient = 0; - // 条件判断,不符合的直接过滤掉 - bool meet_condition = true; - for (const auto& [cond, cond_value] : group.conditions) { - if (!Status.exist(cond)) { - continue; - } - // TODO:这里做成除了不等于,还可计算大于、小于等不同条件的 - int cur_value = Status.get(cond); - if (cur_value != cond_value) { - meet_condition = false; - break; - } - } - if (!meet_condition) { - continue; - } - // necessary里的技能,一个都不能少 - // TODO necessary暂时没做hash校验。因为没有需要比hash的necessary干员( - for (const infrast::SkillsComb& nec_skills : group.necessary) { - auto find_iter = std::find_if( - cur_available_opers.cbegin(), cur_available_opers.cend(), - [&](const infrast::SkillsComb& arg) -> bool { - return arg == nec_skills; - }); - if (find_iter == cur_available_opers.cend()) { - group_unavailable = true; - break; - } - cur_combs.emplace_back(nec_skills); - if (auto iter = nec_skills.efficient_regex.find(m_product); - iter != nec_skills.efficient_regex.cend()) { - cur_efficient += efficient_regex_calc(nec_skills.skills).efficient.at(m_product); - } - else { - cur_efficient += nec_skills.efficient.at(m_product); - } - cur_available_opers.erase(find_iter); - } - if (group_unavailable) { - continue; - } - // 排个序,因为产物不同,效率可能会发生变化,所以配置文件里默认的顺序不一定准确 - auto optional = group.optional; - for (auto&& opt : optional) { - if (auto iter = opt.efficient_regex.find(m_product); - iter != opt.efficient_regex.cend()) { - opt = efficient_regex_calc(opt.skills); - } - } - - std::sort(optional.begin(), optional.end(), - [&](const infrast::SkillsComb& lhs, - const infrast::SkillsComb& rhs) -> bool { - return lhs.efficient.at(m_product) > rhs.efficient.at(m_product); - }); - - // 可能有多个干员有同样的技能,所以这里需要循环找同一个技能,直到找不到为止 - for (const infrast::SkillsComb& opt : optional) { - auto find_iter = cur_available_opers.cbegin(); - while (cur_combs.size() != cur_max_num_of_opers) { - find_iter = std::find_if( - find_iter, cur_available_opers.cend(), - [&](const infrast::SkillsComb& arg) -> bool { - return arg == opt; - }); - if (find_iter != cur_available_opers.cend()) { - // 要求技能匹配的同时,hash也要匹配 - bool hash_matched = false; - if (!opt.possible_hashs.empty()) { - for (const auto& [key, hash] : opt.possible_hashs) { - int dist = utils::hamming(find_iter->name_hash, hash); - Log.trace("optimal_calc | name hash dist", dist, hash, find_iter->name_hash); - if (dist < m_name_hash_thres) { - hash_matched = true; - break; - } - } - } - else { - hash_matched = true; - } - if (!hash_matched) { - ++find_iter; - continue; - } - - cur_combs.emplace_back(opt); - cur_efficient += opt.efficient.at(m_product); - find_iter = cur_available_opers.erase(find_iter); - } - else { - break; - } - } - } - - // 说明可选的没凑满人 - if (cur_combs.size() < cur_max_num_of_opers) { - // 允许外部的话,就把单个干员凑进来 - if (group.allow_external) { - for (size_t i = cur_combs.size(); i != cur_max_num_of_opers; ++i) { - cur_combs.emplace_back(cur_available_opers.at(i)); - cur_efficient += cur_available_opers.at(i).efficient.at(m_product); - } - } - else { // 否则这个组合人不够,就不可用了 - continue; - } - } - { - std::string log_str = "[ "; - for (const auto& comb : cur_combs) { - log_str += comb.desc.empty() ? comb.skills.begin()->names.front() : comb.desc; - log_str += "; "; - } - log_str += "]"; - Log.trace(group.desc, "efficient", cur_efficient, " , skills:", log_str); - } - - if (cur_efficient > max_efficient) { - optimal_combs = std::move(cur_combs); - max_efficient = cur_efficient; - } - } - { - std::string log_str = "[ "; - for (const auto& comb : optimal_combs) { - log_str += comb.desc.empty() ? comb.skills.begin()->names.front() : comb.desc; - log_str += "; "; - } - log_str += "]"; - Log.trace("optimal efficient", max_efficient, " , skills:", log_str); - } - - m_optimal_combs = std::move(optimal_combs); - - return true; -} - -bool asst::InfrastProductionTask::opers_choose() -{ - LogTraceFunction; - bool has_error = false; - - int count = 0; - auto& facility_info = Resrc.infrast().get_facility_info(m_facility); - int cur_max_num_of_opers = facility_info.max_num_of_opers - m_cur_num_of_lokced_opers; - - while (true) { - if (need_exit()) { - return false; - } - const auto image = Ctrler.get_image(); - - InfrastOperImageAnalyzer oper_analyzer(image); - oper_analyzer.set_facility(m_facility); - - if (!oper_analyzer.analyze()) { - return false; - } - oper_analyzer.sort_by_loc(); - - // 这个情况一般是滑动/识别出错了,把所有的干员都滑过去了 - if (oper_analyzer.get_num_of_opers_with_skills() == 0) { - if (!has_error) { - has_error = true; - // 倒回去再来一遍 - swipe_to_the_left_of_operlist(); - continue; - } - else { - // 如果已经出过一次错了,那就可能不是opers_choose出错,而是之前的opers_detect出错了 - return false; - } - } - auto cur_all_opers = oper_analyzer.get_result(); - // 小于心情阈值的干员则不可用 - auto remove_iter = std::remove_if(cur_all_opers.begin(), cur_all_opers.end(), - [&](const infrast::Oper& rhs) -> bool { - return rhs.mood_ratio < m_mood_threshold; - }); - cur_all_opers.erase(remove_iter, cur_all_opers.end()); - - for (auto opt_iter = m_optimal_combs.begin(); opt_iter != m_optimal_combs.end();) { - auto find_iter = std::find_if( - cur_all_opers.cbegin(), cur_all_opers.cend(), - [&](const infrast::Oper& lhs) -> bool { - if (lhs.skills != opt_iter->skills) { - return false; - } - if (!opt_iter->hash_filter) { - return true; - } - else { - // 既要技能相同,也要hash相同,双重校验 - for (const auto& [_, hash] : opt_iter->possible_hashs) { - int dist = utils::hamming(lhs.name_hash, hash); - Log.trace("opers_choose | name hash dist", dist); - if (dist < m_name_hash_thres) { - return true; - } - } - return false; - } - }); - - if (find_iter == cur_all_opers.cend()) { - ++opt_iter; - continue; - } - // 这种情况可能是需要选择两个同样的技能,上一次循环选了一个,但是没有把滑出当前页面,本次又识别到了这个已选择的人 - if (find_iter->selected == true) { - if (cur_max_num_of_opers != 1) { - cur_all_opers.erase(find_iter); - continue; - } - // 但是如果当前设施只有一个位置,即不存在“上次循环”的情况,说明是清除干员按钮没点到 - } - else { - Ctrler.click(find_iter->rect); - } - { - auto avlb_iter = std::find_if( - m_all_available_opers.cbegin(), m_all_available_opers.cend(), - [&](const infrast::Oper& lhs) -> bool { - int dist = utils::hamming(lhs.face_hash, find_iter->face_hash); - Log.trace("opers_choose | face hash dist", dist); - if (dist < m_face_hash_thres) { - return true; - } - return false; - } - ); - if (avlb_iter != m_all_available_opers.cend()) { - m_all_available_opers.erase(avlb_iter); - } - else { - Log.error("opers_choose | not found oper"); - } - } - ++count; - cur_all_opers.erase(find_iter); - opt_iter = m_optimal_combs.erase(opt_iter); - } - if (m_optimal_combs.empty()) { - if (count >= cur_max_num_of_opers) { - break; - } - else { // 这种情况可能是萌新,可用干员人数不足以填满当前设施 - // TODO!!! - break; - } - } - - // 因为识别完了还要点击,所以这里不能异步滑动 - swipe_of_operlist(); - } - - return true; -} - -bool asst::InfrastProductionTask::use_drone() -{ - std::string task_name = "DroneAssist" + m_facility; - ProcessTask task_temp(*this, { task_name }); - return task_temp.run(); -} - -asst::infrast::SkillsComb -asst::InfrastProductionTask::efficient_regex_calc( - std::unordered_set skills) const -{ - infrast::SkillsComb comb(std::move(skills)); - // 根据正则,计算当前干员的实际效率 - for (auto&& [product, formula] : comb.efficient_regex) { - std::string cur_formula = formula; - for (size_t pos = 0; pos != std::string::npos;) { - pos = cur_formula.find('[', pos); - if (pos == std::string::npos) { - break; - } - size_t rp_pos = cur_formula.find(']', pos); - if (rp_pos == std::string::npos) { - break; - // TODO 报错! - } - std::string status_key = cur_formula.substr(pos + 1, rp_pos - pos - 1); - int status_value = Status.get(status_key); - cur_formula.replace(pos, rp_pos - pos + 1, std::to_string(status_value)); - } - - int eff = calculator::eval(cur_formula); - comb.efficient[product] = eff; - } - return comb; -} - -bool asst::InfrastProductionTask::facility_list_detect() -{ - LogTraceFunction; - m_facility_list_tabs.clear(); - - const auto image = Ctrler.get_image(); - MultiMatchImageAnalyzer mm_analyzer(image); - - const auto task_ptr = std::dynamic_pointer_cast( - Task.get("InfrastFacilityListTab" + m_facility)); - mm_analyzer.set_task_info(*task_ptr); - - if (!mm_analyzer.analyze()) { - return false; - } - mm_analyzer.sort_result(); - m_facility_list_tabs.reserve(mm_analyzer.get_result().size()); - for (const auto& res : mm_analyzer.get_result()) { - m_facility_list_tabs.emplace_back(res.rect); - } - - return true; -} +#include "InfrastProductionTask.h" + +#include + +#include + +#include "AsstUtils.hpp" +#include "Controller.h" +#include "InfrastOperImageAnalyzer.h" +#include "Logger.hpp" +#include "MatchImageAnalyzer.h" +#include "MultiMatchImageAnalyzer.h" +#include "Resource.h" +#include "RuntimeStatus.h" +#include "ProcessTask.h" + +bool asst::InfrastProductionTask::shift_facility_list() +{ + LogTraceFunction; + if (!facility_list_detect()) { + return false; + } + if (need_exit()) { + return false; + } + const auto tab_task_ptr = std::dynamic_pointer_cast( + Task.get("InfrastFacilityListTab" + m_facility)); + MatchImageAnalyzer add_analyzer; + const auto add_task_ptr = std::dynamic_pointer_cast( + Task.get("InfrastAddOperator" + m_facility + m_work_mode_name)); + add_analyzer.set_task_info(*add_task_ptr); + + const auto locked_task_ptr = std::dynamic_pointer_cast( + Task.get("InfrastOperLocked" + m_facility)); + MultiMatchImageAnalyzer locked_analyzer; + locked_analyzer.set_task_info(*locked_task_ptr); + + int index = 0; + for (const Rect& tab : m_facility_list_tabs) { + if (need_exit()) { + return false; + } + if (index != 0) { + json::value enter_json = json::object{ + { "facility", m_facility }, + { "index", index } + }; + m_callback(AsstMsg::EnterFacility, enter_json, m_callback_arg); + } + + ++index; + + Ctrler.click(tab); + sleep(tab_task_ptr->rear_delay); + + /* 识别当前制造/贸易站有没有添加干员按钮,没有就不换班 */ + const auto image = Ctrler.get_image(); + add_analyzer.set_image(image); + if (!add_analyzer.analyze()) { + Log.info("no add button, just continue"); + continue; + } + auto& rect = add_analyzer.get_result().rect; + Rect add_button = rect; + auto& rect_move = add_task_ptr->rect_move; + if (!rect_move.empty()) { + add_button.x += rect_move.x; + add_button.y += rect_move.y; + add_button.width = rect_move.width; + add_button.height = rect_move.height; + } + + /* 识别当前正在造什么 */ + MatchImageAnalyzer product_analyzer(image); + auto& all_products = Resrc.infrast().get_facility_info(m_facility).products; + std::string cur_product = all_products.at(0); + double max_score = 0; + for (const std::string& product : all_products) { + const static std::string prefix = "InfrastFlag"; + const auto task_ptr = std::dynamic_pointer_cast( + Task.get(prefix + product)); + product_analyzer.set_task_info(*task_ptr); + if (product_analyzer.analyze()) { + double score = product_analyzer.get_result().score; + if (score > max_score) { + max_score = score; + cur_product = product; + } + } + } + set_product(cur_product); + Log.info("cur product", cur_product); + + locked_analyzer.set_image(image); + if (locked_analyzer.analyze()) { + m_cur_num_of_lokced_opers = locked_analyzer.get_result().size(); + } + else { + m_cur_num_of_lokced_opers = 0; + } + + /* 进入干员选择页面 */ + Ctrler.click(add_button); + sleep(add_task_ptr->rear_delay); + + for (int i = 0; i <= OperSelectRetryTimes; ++i) { + if (need_exit()) { + return false; + } + click_clear_button(); + + if (m_all_available_opers.empty()) { + if (!opers_detect_with_swipe()) { + return false; + } + swipe_to_the_left_of_operlist(2); + } + else { + opers_detect(); + } + optimal_calc(); + bool ret = opers_choose(); + if (!ret) { + m_all_available_opers.clear(); + swipe_to_the_left_of_operlist(2); + continue; + } + break; + } + click_confirm_button(); + + // 使用无人机 + if (cur_product == m_uses_of_drones) { + if (use_drone()) { + m_uses_of_drones = "_Used"; + } + } + } + return true; +} + +bool asst::InfrastProductionTask::opers_detect_with_swipe() +{ + LogTraceFunction; + m_all_available_opers.clear(); + + int first_number = 0; + while (true) { + if (need_exit()) { + return false; + } + size_t num = opers_detect(); + Log.trace("opers_detect return", num); + + // 无论如何也不会没有干员的,除非前面的操作哪里出错了,没进到干员选择的页面。那也就没必要继续下去了 + if (num == 0) { + return false; + } + + // 这里本来是判断不相等就可以退出循环。 + // 但是有时候滑动会把一个干员挡住一半,一个页面完整的干员真的只有10个,所以加个2的差值 + if (max_num_of_opers_per_page - num > 2) { + break; + } + + // 异步在最后会多滑动一下,耽误的时间还不如用同步 + swipe_of_operlist(); + } + + if (!m_all_available_opers.empty()) { + return true; + } + return false; +} + +size_t asst::InfrastProductionTask::opers_detect() +{ + LogTraceFunction; + const auto image = Ctrler.get_image(); + + InfrastOperImageAnalyzer oper_analyzer(image); + oper_analyzer.set_facility(m_facility); + + if (!oper_analyzer.analyze()) { + return 0; + } + const auto& cur_all_opers = oper_analyzer.get_result(); + max_num_of_opers_per_page = (std::max)(max_num_of_opers_per_page, cur_all_opers.size()); + + int cur_available_num = cur_all_opers.size(); + for (const auto& cur_oper : cur_all_opers) { + if (cur_oper.skills.empty()) { + --cur_available_num; + continue; + } + // 心情过低的干员则不可用 + if (cur_oper.mood_ratio < m_mood_threshold) { + //--cur_available_num; + continue; + } + auto find_iter = std::find_if( + m_all_available_opers.cbegin(), m_all_available_opers.cend(), + [&cur_oper](const infrast::Oper& oper) -> bool { + // 有可能是同一个干员,比一下hash + int dist = utils::hamming(cur_oper.face_hash, oper.face_hash); +#ifdef LOG_TRACE + Log.trace("opers_detect hash dist |", dist); +#endif + return dist < m_face_hash_thres; + }); + // 如果两个的hash距离过小,则认为是同一个干员,不进行插入 + if (find_iter != m_all_available_opers.cend()) { + continue; + } + m_all_available_opers.emplace_back(cur_oper); + } + return cur_available_num; +} + +bool asst::InfrastProductionTask::optimal_calc() +{ + LogTraceFunction; + auto& facility_info = Resrc.infrast().get_facility_info(m_facility); + int cur_max_num_of_opers = facility_info.max_num_of_opers - m_cur_num_of_lokced_opers; + + std::vector all_avaliable_combs; + all_avaliable_combs.reserve(m_all_available_opers.size()); + for (auto&& oper : m_all_available_opers) { + auto comb = efficient_regex_calc(oper.skills); + comb.name_hash = oper.name_hash; + all_avaliable_combs.emplace_back(std::move(comb)); + } + + // 先把单个的技能按效率排个序,取效率最高的几个 + std::vector optimal_combs; + optimal_combs.reserve(cur_max_num_of_opers); + double max_efficient = 0; + std::sort(all_avaliable_combs.begin(), all_avaliable_combs.end(), + [&](const infrast::SkillsComb& lhs, const infrast::SkillsComb& rhs) -> bool { + return lhs.efficient.at(m_product) > rhs.efficient.at(m_product); + }); + + for (const auto& comb : all_avaliable_combs) { + std::string skill_str; + for (const auto& skill : comb.skills) { + skill_str += skill.id + " "; + } + Log.trace(skill_str, comb.efficient.at(m_product)); + } + + std::unordered_map skills_num; + for (int i = 0; i != m_all_available_opers.size(); ++i) { + auto comb = all_avaliable_combs.at(i); + + bool out_of_num = false; + for (auto&& skill : comb.skills) { + if (skills_num[skill.id] >= skill.max_num) { + out_of_num = true; + break; + } + } + if (out_of_num) { + continue; + } + + optimal_combs.emplace_back(comb); + max_efficient += all_avaliable_combs.at(i).efficient.at(m_product); + + for (auto&& skill : comb.skills) { + ++skills_num[skill.id]; + } + + if (optimal_combs.size() >= cur_max_num_of_opers) { + break; + } + } + + { + std::string log_str = "[ "; + for (const auto& comb : optimal_combs) { + log_str += comb.desc.empty() ? comb.skills.begin()->names.front() : comb.desc; + log_str += "; "; + } + log_str += "]"; + Log.trace("Single comb efficient", max_efficient, " , skills:", log_str); + } + + // 如果有被锁住的干员,说明当前基建没升满级,组合就不启用 + if (m_cur_num_of_lokced_opers != 0) { + m_optimal_combs = std::move(optimal_combs); + return true; + } + + // 遍历所有组合,找到效率最高的 + auto& all_group = Resrc.infrast().get_skills_group(m_facility); + for (const infrast::SkillsGroup& group : all_group) { + LogTraceScope(group.desc); + auto cur_available_opers = all_avaliable_combs; + bool group_unavailable = false; + std::vector cur_combs; + cur_combs.reserve(cur_max_num_of_opers); + double cur_efficient = 0; + // 条件判断,不符合的直接过滤掉 + bool meet_condition = true; + for (const auto& [cond, cond_value] : group.conditions) { + if (!Status.exist(cond)) { + continue; + } + // TODO:这里做成除了不等于,还可计算大于、小于等不同条件的 + int cur_value = Status.get(cond); + if (cur_value != cond_value) { + meet_condition = false; + break; + } + } + if (!meet_condition) { + continue; + } + // necessary里的技能,一个都不能少 + // TODO necessary暂时没做hash校验。因为没有需要比hash的necessary干员( + for (const infrast::SkillsComb& nec_skills : group.necessary) { + auto find_iter = std::find_if( + cur_available_opers.cbegin(), cur_available_opers.cend(), + [&](const infrast::SkillsComb& arg) -> bool { + return arg == nec_skills; + }); + if (find_iter == cur_available_opers.cend()) { + group_unavailable = true; + break; + } + cur_combs.emplace_back(nec_skills); + if (auto iter = nec_skills.efficient_regex.find(m_product); + iter != nec_skills.efficient_regex.cend()) { + cur_efficient += efficient_regex_calc(nec_skills.skills).efficient.at(m_product); + } + else { + cur_efficient += nec_skills.efficient.at(m_product); + } + cur_available_opers.erase(find_iter); + } + if (group_unavailable) { + continue; + } + // 排个序,因为产物不同,效率可能会发生变化,所以配置文件里默认的顺序不一定准确 + auto optional = group.optional; + for (auto&& opt : optional) { + if (auto iter = opt.efficient_regex.find(m_product); + iter != opt.efficient_regex.cend()) { + opt = efficient_regex_calc(opt.skills); + } + } + + std::sort(optional.begin(), optional.end(), + [&](const infrast::SkillsComb& lhs, + const infrast::SkillsComb& rhs) -> bool { + return lhs.efficient.at(m_product) > rhs.efficient.at(m_product); + }); + + // 可能有多个干员有同样的技能,所以这里需要循环找同一个技能,直到找不到为止 + for (const infrast::SkillsComb& opt : optional) { + auto find_iter = cur_available_opers.cbegin(); + while (cur_combs.size() != cur_max_num_of_opers) { + find_iter = std::find_if( + find_iter, cur_available_opers.cend(), + [&](const infrast::SkillsComb& arg) -> bool { + return arg == opt; + }); + if (find_iter != cur_available_opers.cend()) { + // 要求技能匹配的同时,hash也要匹配 + bool hash_matched = false; + if (!opt.possible_hashs.empty()) { + for (const auto& [key, hash] : opt.possible_hashs) { + int dist = utils::hamming(find_iter->name_hash, hash); + Log.trace("optimal_calc | name hash dist", dist, hash, find_iter->name_hash); + if (dist < m_name_hash_thres) { + hash_matched = true; + break; + } + } + } + else { + hash_matched = true; + } + if (!hash_matched) { + ++find_iter; + continue; + } + + cur_combs.emplace_back(opt); + cur_efficient += opt.efficient.at(m_product); + find_iter = cur_available_opers.erase(find_iter); + } + else { + break; + } + } + } + + // 说明可选的没凑满人 + if (cur_combs.size() < cur_max_num_of_opers) { + // 允许外部的话,就把单个干员凑进来 + if (group.allow_external) { + for (size_t i = cur_combs.size(); i != cur_max_num_of_opers; ++i) { + cur_combs.emplace_back(cur_available_opers.at(i)); + cur_efficient += cur_available_opers.at(i).efficient.at(m_product); + } + } + else { // 否则这个组合人不够,就不可用了 + continue; + } + } + { + std::string log_str = "[ "; + for (const auto& comb : cur_combs) { + log_str += comb.desc.empty() ? comb.skills.begin()->names.front() : comb.desc; + log_str += "; "; + } + log_str += "]"; + Log.trace(group.desc, "efficient", cur_efficient, " , skills:", log_str); + } + + if (cur_efficient > max_efficient) { + optimal_combs = std::move(cur_combs); + max_efficient = cur_efficient; + } + } + { + std::string log_str = "[ "; + for (const auto& comb : optimal_combs) { + log_str += comb.desc.empty() ? comb.skills.begin()->names.front() : comb.desc; + log_str += "; "; + } + log_str += "]"; + Log.trace("optimal efficient", max_efficient, " , skills:", log_str); + } + + m_optimal_combs = std::move(optimal_combs); + + return true; +} + +bool asst::InfrastProductionTask::opers_choose() +{ + LogTraceFunction; + bool has_error = false; + + int count = 0; + auto& facility_info = Resrc.infrast().get_facility_info(m_facility); + int cur_max_num_of_opers = facility_info.max_num_of_opers - m_cur_num_of_lokced_opers; + + while (true) { + if (need_exit()) { + return false; + } + const auto image = Ctrler.get_image(); + + InfrastOperImageAnalyzer oper_analyzer(image); + oper_analyzer.set_facility(m_facility); + + if (!oper_analyzer.analyze()) { + return false; + } + oper_analyzer.sort_by_loc(); + + // 这个情况一般是滑动/识别出错了,把所有的干员都滑过去了 + if (oper_analyzer.get_num_of_opers_with_skills() == 0) { + if (!has_error) { + has_error = true; + // 倒回去再来一遍 + swipe_to_the_left_of_operlist(); + continue; + } + else { + // 如果已经出过一次错了,那就可能不是opers_choose出错,而是之前的opers_detect出错了 + return false; + } + } + auto cur_all_opers = oper_analyzer.get_result(); + // 小于心情阈值的干员则不可用 + auto remove_iter = std::remove_if(cur_all_opers.begin(), cur_all_opers.end(), + [&](const infrast::Oper& rhs) -> bool { + return rhs.mood_ratio < m_mood_threshold; + }); + cur_all_opers.erase(remove_iter, cur_all_opers.end()); + + for (auto opt_iter = m_optimal_combs.begin(); opt_iter != m_optimal_combs.end();) { + auto find_iter = std::find_if( + cur_all_opers.cbegin(), cur_all_opers.cend(), + [&](const infrast::Oper& lhs) -> bool { + if (lhs.skills != opt_iter->skills) { + return false; + } + if (!opt_iter->hash_filter) { + return true; + } + else { + // 既要技能相同,也要hash相同,双重校验 + for (const auto& [_, hash] : opt_iter->possible_hashs) { + int dist = utils::hamming(lhs.name_hash, hash); + Log.trace("opers_choose | name hash dist", dist); + if (dist < m_name_hash_thres) { + return true; + } + } + return false; + } + }); + + if (find_iter == cur_all_opers.cend()) { + ++opt_iter; + continue; + } + // 这种情况可能是需要选择两个同样的技能,上一次循环选了一个,但是没有把滑出当前页面,本次又识别到了这个已选择的人 + if (find_iter->selected == true) { + if (cur_max_num_of_opers != 1) { + cur_all_opers.erase(find_iter); + continue; + } + // 但是如果当前设施只有一个位置,即不存在“上次循环”的情况,说明是清除干员按钮没点到 + } + else { + Ctrler.click(find_iter->rect); + } + { + auto avlb_iter = std::find_if( + m_all_available_opers.cbegin(), m_all_available_opers.cend(), + [&](const infrast::Oper& lhs) -> bool { + int dist = utils::hamming(lhs.face_hash, find_iter->face_hash); + Log.trace("opers_choose | face hash dist", dist); + if (dist < m_face_hash_thres) { + return true; + } + return false; + } + ); + if (avlb_iter != m_all_available_opers.cend()) { + m_all_available_opers.erase(avlb_iter); + } + else { + Log.error("opers_choose | not found oper"); + } + } + ++count; + cur_all_opers.erase(find_iter); + opt_iter = m_optimal_combs.erase(opt_iter); + } + if (m_optimal_combs.empty()) { + if (count >= cur_max_num_of_opers) { + break; + } + else { // 这种情况可能是萌新,可用干员人数不足以填满当前设施 + // TODO!!! + break; + } + } + + // 因为识别完了还要点击,所以这里不能异步滑动 + swipe_of_operlist(); + } + + return true; +} + +bool asst::InfrastProductionTask::use_drone() +{ + std::string task_name = "DroneAssist" + m_facility; + ProcessTask task_temp(*this, { task_name }); + return task_temp.run(); +} + +asst::infrast::SkillsComb +asst::InfrastProductionTask::efficient_regex_calc( + std::unordered_set skills) const +{ + infrast::SkillsComb comb(std::move(skills)); + // 根据正则,计算当前干员的实际效率 + for (auto&& [product, formula] : comb.efficient_regex) { + std::string cur_formula = formula; + for (size_t pos = 0; pos != std::string::npos;) { + pos = cur_formula.find('[', pos); + if (pos == std::string::npos) { + break; + } + size_t rp_pos = cur_formula.find(']', pos); + if (rp_pos == std::string::npos) { + break; + // TODO 报错! + } + std::string status_key = cur_formula.substr(pos + 1, rp_pos - pos - 1); + int status_value = Status.get(status_key); + cur_formula.replace(pos, rp_pos - pos + 1, std::to_string(status_value)); + } + + int eff = calculator::eval(cur_formula); + comb.efficient[product] = eff; + } + return comb; +} + +bool asst::InfrastProductionTask::facility_list_detect() +{ + LogTraceFunction; + m_facility_list_tabs.clear(); + + const auto image = Ctrler.get_image(); + MultiMatchImageAnalyzer mm_analyzer(image); + + const auto task_ptr = std::dynamic_pointer_cast( + Task.get("InfrastFacilityListTab" + m_facility)); + mm_analyzer.set_task_info(*task_ptr); + + if (!mm_analyzer.analyze()) { + return false; + } + mm_analyzer.sort_result(); + m_facility_list_tabs.reserve(mm_analyzer.get_result().size()); + for (const auto& res : mm_analyzer.get_result()) { + m_facility_list_tabs.emplace_back(res.rect); + } + + return true; +} diff --git a/src/MeoAssistant/InfrastReceptionTask.cpp b/src/MeoAssistant/InfrastReceptionTask.cpp index 195ad78d6e..90237513f1 100644 --- a/src/MeoAssistant/InfrastReceptionTask.cpp +++ b/src/MeoAssistant/InfrastReceptionTask.cpp @@ -1,220 +1,220 @@ -#include "InfrastReceptionTask.h" - -#include "Controller.h" -#include "InfrastClueImageAnalyzer.h" -#include "InfrastClueVacancyImageAnalyzer.h" -#include "Logger.hpp" -#include "MatchImageAnalyzer.h" -#include "ProcessTask.h" -#include "Resource.h" - -const std::string asst::InfrastReceptionTask::FacilityName = "Reception"; - -bool asst::InfrastReceptionTask::_run() -{ - json::value task_start_json = json::object{ - { "task_type", "InfrastReceptionTask" }, - { "task_chain", m_task_chain } - }; - m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); - - set_facility(FacilityName); - m_all_available_opers.clear(); - - swipe_to_the_right_of_main_ui(); - enter_facility(FacilityName, 0); - click_bottomleft_tab(); - - close_end_prompt(); - harvest_clue(); - if (need_exit()) { - return false; - } - proc_clue(); - if (need_exit()) { - return false; - } - click_return_button(); - click_bottomleft_tab(); - if (need_exit()) { - return false; - } - shift(); - - return true; -} - -bool asst::InfrastReceptionTask::close_end_prompt() -{ - const auto end_task_ptr = std::dynamic_pointer_cast( - Task.get("EndOfClueExchange")); - MatchImageAnalyzer analyzer(Ctrler.get_image()); - analyzer.set_task_info(*end_task_ptr); - if (!analyzer.analyze()) { - return true; - } - click_return_button(); - sleep(end_task_ptr->rear_delay); - return true; -} - -bool asst::InfrastReceptionTask::harvest_clue() -{ - LogTraceFunction; - - ProcessTask task_temp(*this, { "InfrastClueNew" }); - task_temp.set_retry_times(5); - return task_temp.run(); -} - -bool asst::InfrastReceptionTask::proc_clue() -{ - LogTraceFunction; - const static std::string clue_vacancy = "InfrastClueVacancy"; - const static std::vector clue_suffix = { - "No1", "No2", "No3", "No4", "No5", "No6", "No7" - }; - - proc_vacancy(); - - // 开启线索交流,“解锁线索” - cv::Mat image = Ctrler.get_image(); - MatchImageAnalyzer unlock_analyzer(image); - const auto unlock_task_ptr = std::dynamic_pointer_cast( - Task.get("UnlockClues")); - unlock_analyzer.set_task_info(*unlock_task_ptr); - if (unlock_analyzer.analyze()) { - Ctrler.click(unlock_analyzer.get_result().rect); - sleep(unlock_task_ptr->rear_delay); - click_bottomleft_tab(); - proc_vacancy(); - image = Ctrler.get_image(); - } - - // 所有的空位分析一次,看看还缺哪些线索 - InfrastClueVacancyImageAnalyzer vacancy_analyzer(image); - vacancy_analyzer.set_to_be_analyzed(clue_suffix); - if (!vacancy_analyzer.analyze()) { - } - const auto& vacancy = vacancy_analyzer.get_vacancy(); - for (auto&& [id, _] : vacancy) { - Log.trace("InfrastReceptionTask | Vacancy", id); - } - - std::string product; - if (vacancy.size() == 1) { - product = vacancy.begin()->first; - } - else { - product = "General"; - } - Log.trace("InfrastReceptionTask | product", product); - set_product(product); - - return true; -} - -bool asst::InfrastReceptionTask::proc_vacancy() -{ - LogTraceFunction; - const static std::string clue_vacancy = "InfrastClueVacancy"; - const static std::vector clue_suffix = { - "No1", "No2", "No3", "No4", "No5", "No6", "No7" - }; - - cv::Mat image = Ctrler.get_image(); - for (const std::string& clue : clue_suffix) { - if (need_exit()) { - return false; - } - // 先识别线索的空位 - InfrastClueVacancyImageAnalyzer vacancy_analyzer(image); - vacancy_analyzer.set_to_be_analyzed({ clue }); - if (!vacancy_analyzer.analyze()) { - continue; - } - // 点开线索的空位 - Rect vacancy = vacancy_analyzer.get_vacancy().cbegin()->second; - Ctrler.click(vacancy); - int delay = Task.get(clue_vacancy + clue)->rear_delay; - sleep(delay); - - // 识别右边列表中的线索,然后用最底下的那个(一般都是剩余时间最短的) - //swipe_to_the_bottom_of_clue_list_on_the_right(); - image = Ctrler.get_image(); - InfrastClueImageAnalyzer clue_analyzer(image); - if (!clue_analyzer.analyze()) { - continue; - } - Ctrler.click(clue_analyzer.get_result().back().first); - sleep(delay); - } - return true; -} - -bool asst::InfrastReceptionTask::shift() -{ - LogTraceFunction; - const auto image = Ctrler.get_image(); - MatchImageAnalyzer add_analyzer(image); - - const auto raw_task_ptr = Task.get("InfrastAddOperator" + m_facility + m_work_mode_name); - switch (raw_task_ptr->algorithm) { - case AlgorithmType::JustReturn: - if (raw_task_ptr->action == ProcessTaskAction::ClickRect) { - Ctrler.click(raw_task_ptr->specific_rect); - } - break; - case AlgorithmType::MatchTemplate: { - const auto add_task_ptr = std::dynamic_pointer_cast(raw_task_ptr); - add_analyzer.set_task_info(*add_task_ptr); - - if (!add_analyzer.analyze()) { - return true; - } - Ctrler.click(add_analyzer.get_result().rect); - } break; - default: - break; - } - sleep(raw_task_ptr->rear_delay); - - for (int i = 0; i <= OperSelectRetryTimes; ++i) { - if (need_exit()) { - return false; - } - click_clear_button(); - - if (!opers_detect_with_swipe()) { - return false; - } - swipe_to_the_left_of_operlist(); - - optimal_calc(); - bool ret = opers_choose(); - if (!ret) { - m_all_available_opers.clear(); - swipe_to_the_left_of_operlist(); - continue; - } - break; - } - click_confirm_button(); - return true; -} - -bool asst::InfrastReceptionTask::swipe_to_the_bottom_of_clue_list_on_the_right() -{ - LogTraceFunction; - static Rect begin_rect = Task.get("InfrastClueOnTheRightSwipeBegin")->specific_rect; - static Rect end_rect = Task.get("InfrastClueOnTheRightSwipeEnd")->specific_rect; - static int duration = Task.get("InfrastClueOnTheRightSwipeBegin")->pre_delay; - static int extra_delay = Task.get("InfrastClueOnTheRightSwipeBegin")->rear_delay; - static int loop_times = Task.get("InfrastClueOnTheRightSwipeBegin")->max_times; - - for (int i = 0; i != loop_times; ++i) { - Ctrler.swipe(begin_rect, end_rect, duration, true, 0, false); - } - sleep(extra_delay); - return false; -} \ No newline at end of file +#include "InfrastReceptionTask.h" + +#include "Controller.h" +#include "InfrastClueImageAnalyzer.h" +#include "InfrastClueVacancyImageAnalyzer.h" +#include "Logger.hpp" +#include "MatchImageAnalyzer.h" +#include "ProcessTask.h" +#include "Resource.h" + +const std::string asst::InfrastReceptionTask::FacilityName = "Reception"; + +bool asst::InfrastReceptionTask::_run() +{ + json::value task_start_json = json::object{ + { "task_type", "InfrastReceptionTask" }, + { "task_chain", m_task_chain } + }; + m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); + + set_facility(FacilityName); + m_all_available_opers.clear(); + + swipe_to_the_right_of_main_ui(); + enter_facility(FacilityName, 0); + click_bottomleft_tab(); + + close_end_prompt(); + harvest_clue(); + if (need_exit()) { + return false; + } + proc_clue(); + if (need_exit()) { + return false; + } + click_return_button(); + click_bottomleft_tab(); + if (need_exit()) { + return false; + } + shift(); + + return true; +} + +bool asst::InfrastReceptionTask::close_end_prompt() +{ + const auto end_task_ptr = std::dynamic_pointer_cast( + Task.get("EndOfClueExchange")); + MatchImageAnalyzer analyzer(Ctrler.get_image()); + analyzer.set_task_info(*end_task_ptr); + if (!analyzer.analyze()) { + return true; + } + click_return_button(); + sleep(end_task_ptr->rear_delay); + return true; +} + +bool asst::InfrastReceptionTask::harvest_clue() +{ + LogTraceFunction; + + ProcessTask task_temp(*this, { "InfrastClueNew" }); + task_temp.set_retry_times(5); + return task_temp.run(); +} + +bool asst::InfrastReceptionTask::proc_clue() +{ + LogTraceFunction; + const static std::string clue_vacancy = "InfrastClueVacancy"; + const static std::vector clue_suffix = { + "No1", "No2", "No3", "No4", "No5", "No6", "No7" + }; + + proc_vacancy(); + + // 开启线索交流,“解锁线索” + cv::Mat image = Ctrler.get_image(); + MatchImageAnalyzer unlock_analyzer(image); + const auto unlock_task_ptr = std::dynamic_pointer_cast( + Task.get("UnlockClues")); + unlock_analyzer.set_task_info(*unlock_task_ptr); + if (unlock_analyzer.analyze()) { + Ctrler.click(unlock_analyzer.get_result().rect); + sleep(unlock_task_ptr->rear_delay); + click_bottomleft_tab(); + proc_vacancy(); + image = Ctrler.get_image(); + } + + // 所有的空位分析一次,看看还缺哪些线索 + InfrastClueVacancyImageAnalyzer vacancy_analyzer(image); + vacancy_analyzer.set_to_be_analyzed(clue_suffix); + if (!vacancy_analyzer.analyze()) { + } + const auto& vacancy = vacancy_analyzer.get_vacancy(); + for (auto&& [id, _] : vacancy) { + Log.trace("InfrastReceptionTask | Vacancy", id); + } + + std::string product; + if (vacancy.size() == 1) { + product = vacancy.begin()->first; + } + else { + product = "General"; + } + Log.trace("InfrastReceptionTask | product", product); + set_product(product); + + return true; +} + +bool asst::InfrastReceptionTask::proc_vacancy() +{ + LogTraceFunction; + const static std::string clue_vacancy = "InfrastClueVacancy"; + const static std::vector clue_suffix = { + "No1", "No2", "No3", "No4", "No5", "No6", "No7" + }; + + cv::Mat image = Ctrler.get_image(); + for (const std::string& clue : clue_suffix) { + if (need_exit()) { + return false; + } + // 先识别线索的空位 + InfrastClueVacancyImageAnalyzer vacancy_analyzer(image); + vacancy_analyzer.set_to_be_analyzed({ clue }); + if (!vacancy_analyzer.analyze()) { + continue; + } + // 点开线索的空位 + Rect vacancy = vacancy_analyzer.get_vacancy().cbegin()->second; + Ctrler.click(vacancy); + int delay = Task.get(clue_vacancy + clue)->rear_delay; + sleep(delay); + + // 识别右边列表中的线索,然后用最底下的那个(一般都是剩余时间最短的) + //swipe_to_the_bottom_of_clue_list_on_the_right(); + image = Ctrler.get_image(); + InfrastClueImageAnalyzer clue_analyzer(image); + if (!clue_analyzer.analyze()) { + continue; + } + Ctrler.click(clue_analyzer.get_result().back().first); + sleep(delay); + } + return true; +} + +bool asst::InfrastReceptionTask::shift() +{ + LogTraceFunction; + const auto image = Ctrler.get_image(); + MatchImageAnalyzer add_analyzer(image); + + const auto raw_task_ptr = Task.get("InfrastAddOperator" + m_facility + m_work_mode_name); + switch (raw_task_ptr->algorithm) { + case AlgorithmType::JustReturn: + if (raw_task_ptr->action == ProcessTaskAction::ClickRect) { + Ctrler.click(raw_task_ptr->specific_rect); + } + break; + case AlgorithmType::MatchTemplate: { + const auto add_task_ptr = std::dynamic_pointer_cast(raw_task_ptr); + add_analyzer.set_task_info(*add_task_ptr); + + if (!add_analyzer.analyze()) { + return true; + } + Ctrler.click(add_analyzer.get_result().rect); + } break; + default: + break; + } + sleep(raw_task_ptr->rear_delay); + + for (int i = 0; i <= OperSelectRetryTimes; ++i) { + if (need_exit()) { + return false; + } + click_clear_button(); + + if (!opers_detect_with_swipe()) { + return false; + } + swipe_to_the_left_of_operlist(); + + optimal_calc(); + bool ret = opers_choose(); + if (!ret) { + m_all_available_opers.clear(); + swipe_to_the_left_of_operlist(); + continue; + } + break; + } + click_confirm_button(); + return true; +} + +bool asst::InfrastReceptionTask::swipe_to_the_bottom_of_clue_list_on_the_right() +{ + LogTraceFunction; + static Rect begin_rect = Task.get("InfrastClueOnTheRightSwipeBegin")->specific_rect; + static Rect end_rect = Task.get("InfrastClueOnTheRightSwipeEnd")->specific_rect; + static int duration = Task.get("InfrastClueOnTheRightSwipeBegin")->pre_delay; + static int extra_delay = Task.get("InfrastClueOnTheRightSwipeBegin")->rear_delay; + static int loop_times = Task.get("InfrastClueOnTheRightSwipeBegin")->max_times; + + for (int i = 0; i != loop_times; ++i) { + Ctrler.swipe(begin_rect, end_rect, duration, true, 0, false); + } + sleep(extra_delay); + return false; +} diff --git a/src/MeoAssistant/ItemConfiger.cpp b/src/MeoAssistant/ItemConfiger.cpp index c180ce6f85..1a0e80cc87 100644 --- a/src/MeoAssistant/ItemConfiger.cpp +++ b/src/MeoAssistant/ItemConfiger.cpp @@ -9,4 +9,4 @@ bool asst::ItemConfiger::parse(const json::value& json) m_item_name.emplace(id, std::move(name)); } return true; -} \ No newline at end of file +} diff --git a/src/MeoAssistant/Logger.hpp b/src/MeoAssistant/Logger.hpp index d2c14d2170..dbfab5e057 100644 --- a/src/MeoAssistant/Logger.hpp +++ b/src/MeoAssistant/Logger.hpp @@ -1,199 +1,199 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "AsstUtils.hpp" -#include "Version.h" - -namespace asst -{ - class Logger - { - public: - ~Logger() - { - flush(); - } - - Logger(const Logger&) = delete; - Logger(Logger&&) = delete; - - static Logger& get_instance() - { - static Logger _unique_instance; - return _unique_instance; - } - - static void set_dirname(std::string dirname) noexcept - { - m_dirname = std::move(dirname); - } - - template - inline void trace(Args&&... args) - { - std::string_view level = "TRC"; - log(level, std::forward(args)...); - } - template - inline void info(Args&&... args) - { - std::string_view level = "INF"; - log(level, std::forward(args)...); - } - template - inline void error(Args&&... args) - { - std::string_view level = "ERR"; - log(level, std::forward(args)...); - } - void flush() - { - std::unique_lock trace_lock(m_trace_mutex); - if (m_ofs.is_open()) { - m_ofs.close(); - } - } - - const std::string m_log_filename = m_dirname + "asst.log"; - const std::string m_log_bak_filename = m_dirname + "asst.bak.log"; - - private: - Logger() - { - check_filesize_and_remove(); - log_init_info(); - } - - void check_filesize_and_remove() - { - constexpr uintmax_t MaxLogSize = 4 * 1024 * 1024; - try { - if (std::filesystem::exists(m_log_filename)) { - uintmax_t log_size = std::filesystem::file_size(m_log_filename); - if (log_size >= MaxLogSize) { - std::filesystem::rename(m_log_filename, m_log_bak_filename); - } - } - } - catch (...) { - ; - } - } - void log_init_info() - { - trace("-----------------------------"); - trace("MeoAssistant Process Start"); - trace("Version", asst::Version); - trace("Build DataTime", __DATE__, __TIME__); - trace("Working Path", m_dirname); - trace("-----------------------------"); - } - - template - void log(std::string_view level, Args&&... args) - { - std::unique_lock trace_lock(m_trace_mutex); - - constexpr int buff_len = 128; - char buff[buff_len] = { 0 }; -#ifdef _WIN32 -#ifdef _MSC_VER - sprintf_s(buff, buff_len, -#else // ! _MSC_VER - sprintf(buff, -#endif // END _MSC_VER - "[%s][%s][Px%x][Tx%x]", - asst::utils::get_format_time().c_str(), - level.data(), _getpid(), ::GetCurrentThreadId() - ); -#else // ! _WIN32 - sprintf(buff, "[%s][%s][Px%x][Tx%x]", - asst::utils::get_format_time().c_str(), - level.data(), getpid(), gettid() - ); -#endif // END _WIN32 - - if (!m_ofs.is_open()) { - m_ofs = std::ofstream(m_log_filename, std::ios::out | std::ios::app); - } -#ifdef LOG_TRACE - stream_args(m_ofs, buff, args...); -#else - stream_args(m_ofs, buff, std::forward(args)...); -#endif - -#ifdef LOG_TRACE - stream_args(std::cout, buff, std::forward(args)...); -#endif - } - - template - inline void stream_args(std::ostream& os, T&& first, Args&&... rest) - { - stream()(os, std::forward(first)); - stream_args(os, std::forward(rest)...); - } - template - inline void stream_args(std::ostream& os) - { - os << std::endl; - } - - template - struct stream - { - inline void operator()(std::ostream& os, T&& first) - { - os << first << " "; - } - }; - template - struct stream::value>::type> - { - inline void operator()(std::ostream& os, T&& first) - { -#ifdef _WIN32 - os << utils::utf8_to_gbk(first) << " "; -#else - os << first << " "; // Don't fucking use gbk in linux -#endif - } - }; - - inline static std::string m_dirname; - std::mutex m_trace_mutex; - std::ofstream m_ofs; - }; - - class LoggerAux - { - public: - LoggerAux(const std::string& func_name) - : m_func_name(func_name), - m_start_time(std::chrono::steady_clock::now()) - { - Logger::get_instance().trace(m_func_name, " | enter"); - } - ~LoggerAux() - { - auto duration = std::chrono::steady_clock::now() - m_start_time; - Logger::get_instance().trace(m_func_name, " | leave,", - std::chrono::duration_cast(duration).count(), "ms"); - } - - private: - std::string m_func_name; - std::chrono::time_point m_start_time; - }; - - //static auto& log = Logger::get_instance(); -#define Log Logger::get_instance() -#define LogTraceFunction LoggerAux _func_aux(__FUNCTION__) -#define LogTraceScope LoggerAux _func_aux -} +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "AsstUtils.hpp" +#include "Version.h" + +namespace asst +{ + class Logger + { + public: + ~Logger() + { + flush(); + } + + Logger(const Logger&) = delete; + Logger(Logger&&) = delete; + + static Logger& get_instance() + { + static Logger _unique_instance; + return _unique_instance; + } + + static void set_dirname(std::string dirname) noexcept + { + m_dirname = std::move(dirname); + } + + template + inline void trace(Args&&... args) + { + std::string_view level = "TRC"; + log(level, std::forward(args)...); + } + template + inline void info(Args&&... args) + { + std::string_view level = "INF"; + log(level, std::forward(args)...); + } + template + inline void error(Args&&... args) + { + std::string_view level = "ERR"; + log(level, std::forward(args)...); + } + void flush() + { + std::unique_lock trace_lock(m_trace_mutex); + if (m_ofs.is_open()) { + m_ofs.close(); + } + } + + const std::string m_log_filename = m_dirname + "asst.log"; + const std::string m_log_bak_filename = m_dirname + "asst.bak.log"; + + private: + Logger() + { + check_filesize_and_remove(); + log_init_info(); + } + + void check_filesize_and_remove() + { + constexpr uintmax_t MaxLogSize = 4 * 1024 * 1024; + try { + if (std::filesystem::exists(m_log_filename)) { + uintmax_t log_size = std::filesystem::file_size(m_log_filename); + if (log_size >= MaxLogSize) { + std::filesystem::rename(m_log_filename, m_log_bak_filename); + } + } + } + catch (...) { + ; + } + } + void log_init_info() + { + trace("-----------------------------"); + trace("MeoAssistant Process Start"); + trace("Version", asst::Version); + trace("Build DataTime", __DATE__, __TIME__); + trace("Working Path", m_dirname); + trace("-----------------------------"); + } + + template + void log(std::string_view level, Args&&... args) + { + std::unique_lock trace_lock(m_trace_mutex); + + constexpr int buff_len = 128; + char buff[buff_len] = { 0 }; +#ifdef _WIN32 +#ifdef _MSC_VER + sprintf_s(buff, buff_len, +#else // ! _MSC_VER + sprintf(buff, +#endif // END _MSC_VER + "[%s][%s][Px%x][Tx%x]", + asst::utils::get_format_time().c_str(), + level.data(), _getpid(), ::GetCurrentThreadId() + ); +#else // ! _WIN32 + sprintf(buff, "[%s][%s][Px%x][Tx%x]", + asst::utils::get_format_time().c_str(), + level.data(), getpid(), gettid() + ); +#endif // END _WIN32 + + if (!m_ofs.is_open()) { + m_ofs = std::ofstream(m_log_filename, std::ios::out | std::ios::app); + } +#ifdef LOG_TRACE + stream_args(m_ofs, buff, args...); +#else + stream_args(m_ofs, buff, std::forward(args)...); +#endif + +#ifdef LOG_TRACE + stream_args(std::cout, buff, std::forward(args)...); +#endif + } + + template + inline void stream_args(std::ostream& os, T&& first, Args&&... rest) + { + stream()(os, std::forward(first)); + stream_args(os, std::forward(rest)...); + } + template + inline void stream_args(std::ostream& os) + { + os << std::endl; + } + + template + struct stream + { + inline void operator()(std::ostream& os, T&& first) + { + os << first << " "; + } + }; + template + struct stream::value>::type> + { + inline void operator()(std::ostream& os, T&& first) + { +#ifdef _WIN32 + os << utils::utf8_to_gbk(first) << " "; +#else + os << first << " "; // Don't fucking use gbk in linux +#endif + } + }; + + inline static std::string m_dirname; + std::mutex m_trace_mutex; + std::ofstream m_ofs; + }; + + class LoggerAux + { + public: + LoggerAux(const std::string& func_name) + : m_func_name(func_name), + m_start_time(std::chrono::steady_clock::now()) + { + Logger::get_instance().trace(m_func_name, " | enter"); + } + ~LoggerAux() + { + auto duration = std::chrono::steady_clock::now() - m_start_time; + Logger::get_instance().trace(m_func_name, " | leave,", + std::chrono::duration_cast(duration).count(), "ms"); + } + + private: + std::string m_func_name; + std::chrono::time_point m_start_time; + }; + + //static auto& log = Logger::get_instance(); +#define Log Logger::get_instance() +#define LogTraceFunction LoggerAux _func_aux(__FUNCTION__) +#define LogTraceScope LoggerAux _func_aux +} diff --git a/src/MeoAssistant/OcrImageAnalyzer.cpp b/src/MeoAssistant/OcrImageAnalyzer.cpp index 579869c678..7e87ff9701 100644 --- a/src/MeoAssistant/OcrImageAnalyzer.cpp +++ b/src/MeoAssistant/OcrImageAnalyzer.cpp @@ -1,69 +1,69 @@ -#include "OcrImageAnalyzer.h" - -#include - -#include "Logger.hpp" -#include "Resource.h" -#include "AipOcr.h" - -bool asst::OcrImageAnalyzer::analyze() -{ - m_ocr_result.clear(); - - std::vector preds_vec; - - if (!m_replace.empty()) { - TextRectProc text_replace = [&](TextRect& tr) -> bool { - for (const auto& [regex, new_str] : m_replace) { - tr.text = std::regex_replace(tr.text, std::regex(regex), new_str); - } - return true; - }; - preds_vec.emplace_back(text_replace); - } - - if (!m_required.empty()) { - if (m_full_match) { - TextRectProc required_match = [&](TextRect& tr) -> bool { - return std::find(m_required.cbegin(), m_required.cend(), tr.text) != m_required.cend(); - }; - preds_vec.emplace_back(required_match); - } - else { - TextRectProc required_search = [&](TextRect& tr) -> bool { - auto is_sub = [&tr](const std::string& str) -> bool { - return tr.text.find(str) != std::string::npos; - }; - return std::find_if(m_required.cbegin(), m_required.cend(), is_sub) != m_required.cend(); - }; - preds_vec.emplace_back(required_search); - } - } - - preds_vec.emplace_back(m_pred); - - TextRectProc all_pred = [&](TextRect& tr) -> bool { - for (auto pred : preds_vec) { - if (pred && !pred(tr)) { - return false; - } - } - return true; - }; - - auto& aip_ocr = Resrc.cfg().get_options().aip_ocr; - bool need_local = true; - if (aip_ocr.enable) { - if (aip_ocr.accurate) { - need_local = !AipOcr::get_instance().request_ocr_accurate(m_image, m_ocr_result, all_pred); - } - else { - need_local = !AipOcr::get_instance().request_ocr_general(m_image, m_ocr_result, all_pred); - } - } - if (need_local) { - m_ocr_result = Resrc.ocr().recognize(m_image, m_roi, all_pred, m_without_det); - } - //log.trace("ocr result", m_ocr_result); - return !m_ocr_result.empty(); -} +#include "OcrImageAnalyzer.h" + +#include + +#include "Logger.hpp" +#include "Resource.h" +#include "AipOcr.h" + +bool asst::OcrImageAnalyzer::analyze() +{ + m_ocr_result.clear(); + + std::vector preds_vec; + + if (!m_replace.empty()) { + TextRectProc text_replace = [&](TextRect& tr) -> bool { + for (const auto& [regex, new_str] : m_replace) { + tr.text = std::regex_replace(tr.text, std::regex(regex), new_str); + } + return true; + }; + preds_vec.emplace_back(text_replace); + } + + if (!m_required.empty()) { + if (m_full_match) { + TextRectProc required_match = [&](TextRect& tr) -> bool { + return std::find(m_required.cbegin(), m_required.cend(), tr.text) != m_required.cend(); + }; + preds_vec.emplace_back(required_match); + } + else { + TextRectProc required_search = [&](TextRect& tr) -> bool { + auto is_sub = [&tr](const std::string& str) -> bool { + return tr.text.find(str) != std::string::npos; + }; + return std::find_if(m_required.cbegin(), m_required.cend(), is_sub) != m_required.cend(); + }; + preds_vec.emplace_back(required_search); + } + } + + preds_vec.emplace_back(m_pred); + + TextRectProc all_pred = [&](TextRect& tr) -> bool { + for (auto pred : preds_vec) { + if (pred && !pred(tr)) { + return false; + } + } + return true; + }; + + auto& aip_ocr = Resrc.cfg().get_options().aip_ocr; + bool need_local = true; + if (aip_ocr.enable) { + if (aip_ocr.accurate) { + need_local = !AipOcr::get_instance().request_ocr_accurate(m_image, m_ocr_result, all_pred); + } + else { + need_local = !AipOcr::get_instance().request_ocr_general(m_image, m_ocr_result, all_pred); + } + } + if (need_local) { + m_ocr_result = Resrc.ocr().recognize(m_image, m_roi, all_pred, m_without_det); + } + //log.trace("ocr result", m_ocr_result); + return !m_ocr_result.empty(); +} diff --git a/src/MeoAssistant/OcrImageAnalyzer.h b/src/MeoAssistant/OcrImageAnalyzer.h index 38c7ea3532..9eb18a1346 100644 --- a/src/MeoAssistant/OcrImageAnalyzer.h +++ b/src/MeoAssistant/OcrImageAnalyzer.h @@ -1,64 +1,64 @@ -#pragma once -#include "AbstractImageAnalyzer.h" - -#include -#include -#include - -#include "AsstDef.h" - -namespace asst -{ - class OcrImageAnalyzer : public AbstractImageAnalyzer - { - public: - using AbstractImageAnalyzer::AbstractImageAnalyzer; - virtual ~OcrImageAnalyzer() = default; - - virtual bool analyze() override; - - void set_required(std::vector required, bool full_match = false) noexcept - { - m_required = std::move(required); - m_full_match = full_match; - } - void set_replace(std::unordered_map replace) noexcept - { - m_replace = std::move(replace); - } - void set_task_info(OcrTaskInfo task_info) noexcept - { - m_required = std::move(task_info.text); - m_full_match = task_info.need_full_match; - m_replace = std::move(task_info.replace_map); - - set_roi(task_info.roi); - correct_roi(); - auto& cache_roi = task_info.region_of_appeared; - if (task_info.cache && !cache_roi.empty()) { - m_roi = cache_roi; - m_without_det = true; - } - else { - m_without_det = false; - } - } - - void set_pred(const TextRectProc& pred) - { - m_pred = pred; - } - const std::vector& get_result() const noexcept - { - return m_ocr_result; - } - - protected: - std::vector m_ocr_result; - std::vector m_required; - bool m_full_match = false; - std::unordered_map m_replace; - TextRectProc m_pred = nullptr; - bool m_without_det = false; - }; -} +#pragma once +#include "AbstractImageAnalyzer.h" + +#include +#include +#include + +#include "AsstDef.h" + +namespace asst +{ + class OcrImageAnalyzer : public AbstractImageAnalyzer + { + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + virtual ~OcrImageAnalyzer() = default; + + virtual bool analyze() override; + + void set_required(std::vector required, bool full_match = false) noexcept + { + m_required = std::move(required); + m_full_match = full_match; + } + void set_replace(std::unordered_map replace) noexcept + { + m_replace = std::move(replace); + } + void set_task_info(OcrTaskInfo task_info) noexcept + { + m_required = std::move(task_info.text); + m_full_match = task_info.need_full_match; + m_replace = std::move(task_info.replace_map); + + set_roi(task_info.roi); + correct_roi(); + auto& cache_roi = task_info.region_of_appeared; + if (task_info.cache && !cache_roi.empty()) { + m_roi = cache_roi; + m_without_det = true; + } + else { + m_without_det = false; + } + } + + void set_pred(const TextRectProc& pred) + { + m_pred = pred; + } + const std::vector& get_result() const noexcept + { + return m_ocr_result; + } + + protected: + std::vector m_ocr_result; + std::vector m_required; + bool m_full_match = false; + std::unordered_map m_replace; + TextRectProc m_pred = nullptr; + bool m_without_det = false; + }; +} diff --git a/src/MeoAssistant/OcrPack.cpp b/src/MeoAssistant/OcrPack.cpp index 631f506483..37253a9567 100644 --- a/src/MeoAssistant/OcrPack.cpp +++ b/src/MeoAssistant/OcrPack.cpp @@ -1,115 +1,115 @@ -#include "OcrPack.h" - -#include -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" - -asst::OcrPack::~OcrPack() -{ - PaddleOcrDestroy(m_ocr); -} - -bool asst::OcrPack::load(const std::string& dir) -{ - constexpr static const char* DetName = "/det"; - //constexpr static const char* ClsName = "/cls"; - constexpr static const char* RecName = "/rec"; - constexpr static const char* KeysName = "/ppocr_keys_v1.txt"; - - const std::string dst_filename = dir + DetName; - //const std::string cls_filename = dir + ClsName; - const std::string rec_filename = dir + RecName; - const std::string keys_filename = dir + KeysName; - - if (m_ocr != nullptr) { - PaddleOcrDestroy(m_ocr); - } - m_ocr = PaddleOcrCreate(dst_filename.c_str(), rec_filename.c_str(), keys_filename.c_str(), nullptr); - - return m_ocr != nullptr; -} - -std::vector asst::OcrPack::recognize(const cv::Mat image, const asst::TextRectProc& pred, bool without_det) -{ - LogTraceFunction; - - std::vector buf; - cv::imencode(".png", image, buf); - - constexpr static size_t MaxBoxSize = 128; - // each box has 8 value ( 4 points, x and y ) - int boxes[MaxBoxSize * 8] = { 0 }; - char* strs[MaxBoxSize] = { 0 }; - for (size_t i = 0; i != MaxBoxSize; ++i) { - constexpr static size_t MaxTextSize = 1024; - *(strs + i) = new char[MaxTextSize]; - memset(*(strs + i), 0, MaxTextSize); - } - float scores[MaxBoxSize] = { 0 }; - size_t size = 0; - - Log.trace("Without Det:", without_det); - if (!without_det) { - PaddleOcrSystem(m_ocr, buf.data(), buf.size(), false, - boxes, strs, scores, &size, nullptr, nullptr); - } - else { - PaddleOcrRec(m_ocr, buf.data(), buf.size(), - strs, scores, &size, nullptr, nullptr); - } - - std::vector result; - std::string log_str_raw; - std::string log_str_proc; - for (size_t i = 0; i != size; ++i) { - // the box rect like ↓ - // 0 - 1 - // 3 - 2 - Rect rect; - if (!without_det) { - int* box = boxes + i * 8; - int x_collect[4] = { *(box + 0), *(box + 2), *(box + 4), *(box + 6) }; - int y_collect[4] = { *(box + 1), *(box + 3), *(box + 5), *(box + 7) }; - int left = int(*std::min_element(x_collect, x_collect + 4)); - int right = int(*std::max_element(x_collect, x_collect + 4)); - int top = int(*std::min_element(y_collect, y_collect + 4)); - int bottom = int(*std::max_element(y_collect, y_collect + 4)); - rect = Rect(left, top, right - left, bottom - top); - } - std::string text(*(strs + i)); - float score = *(scores + i); - - TextRect tr{ text, rect, score }; - - log_str_raw += tr.to_string() + ", "; - if (!pred || pred(tr)) { - log_str_proc += tr.to_string() + ", "; - result.emplace_back(std::move(tr)); - } - } - for (size_t i = 0; i != MaxBoxSize; ++i) { - delete[] * (strs + i); - } - - Log.trace("OcrPack::recognize | raw : ", log_str_raw); - Log.trace("OcrPack::recognize | proc : ", log_str_proc); - return result; -} - -std::vector asst::OcrPack::recognize(const cv::Mat image, const asst::Rect& roi, const asst::TextRectProc& pred, bool without_det) -{ - auto rect_cor = [&roi, &pred, &without_det](TextRect& tr) -> bool { - if (without_det) { - tr.rect = roi; - } - else { - tr.rect.x += roi.x; - tr.rect.y += roi.y; - } - return pred(tr); - }; - Log.trace("OcrPack::recognize | roi : ", roi.to_string()); - return recognize(image(utils::make_rect(roi)), rect_cor, without_det); -} +#include "OcrPack.h" + +#include +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" + +asst::OcrPack::~OcrPack() +{ + PaddleOcrDestroy(m_ocr); +} + +bool asst::OcrPack::load(const std::string& dir) +{ + constexpr static const char* DetName = "/det"; + //constexpr static const char* ClsName = "/cls"; + constexpr static const char* RecName = "/rec"; + constexpr static const char* KeysName = "/ppocr_keys_v1.txt"; + + const std::string dst_filename = dir + DetName; + //const std::string cls_filename = dir + ClsName; + const std::string rec_filename = dir + RecName; + const std::string keys_filename = dir + KeysName; + + if (m_ocr != nullptr) { + PaddleOcrDestroy(m_ocr); + } + m_ocr = PaddleOcrCreate(dst_filename.c_str(), rec_filename.c_str(), keys_filename.c_str(), nullptr); + + return m_ocr != nullptr; +} + +std::vector asst::OcrPack::recognize(const cv::Mat image, const asst::TextRectProc& pred, bool without_det) +{ + LogTraceFunction; + + std::vector buf; + cv::imencode(".png", image, buf); + + constexpr static size_t MaxBoxSize = 128; + // each box has 8 value ( 4 points, x and y ) + int boxes[MaxBoxSize * 8] = { 0 }; + char* strs[MaxBoxSize] = { 0 }; + for (size_t i = 0; i != MaxBoxSize; ++i) { + constexpr static size_t MaxTextSize = 1024; + *(strs + i) = new char[MaxTextSize]; + memset(*(strs + i), 0, MaxTextSize); + } + float scores[MaxBoxSize] = { 0 }; + size_t size = 0; + + Log.trace("Without Det:", without_det); + if (!without_det) { + PaddleOcrSystem(m_ocr, buf.data(), buf.size(), false, + boxes, strs, scores, &size, nullptr, nullptr); + } + else { + PaddleOcrRec(m_ocr, buf.data(), buf.size(), + strs, scores, &size, nullptr, nullptr); + } + + std::vector result; + std::string log_str_raw; + std::string log_str_proc; + for (size_t i = 0; i != size; ++i) { + // the box rect like ↓ + // 0 - 1 + // 3 - 2 + Rect rect; + if (!without_det) { + int* box = boxes + i * 8; + int x_collect[4] = { *(box + 0), *(box + 2), *(box + 4), *(box + 6) }; + int y_collect[4] = { *(box + 1), *(box + 3), *(box + 5), *(box + 7) }; + int left = int(*std::min_element(x_collect, x_collect + 4)); + int right = int(*std::max_element(x_collect, x_collect + 4)); + int top = int(*std::min_element(y_collect, y_collect + 4)); + int bottom = int(*std::max_element(y_collect, y_collect + 4)); + rect = Rect(left, top, right - left, bottom - top); + } + std::string text(*(strs + i)); + float score = *(scores + i); + + TextRect tr{ text, rect, score }; + + log_str_raw += tr.to_string() + ", "; + if (!pred || pred(tr)) { + log_str_proc += tr.to_string() + ", "; + result.emplace_back(std::move(tr)); + } + } + for (size_t i = 0; i != MaxBoxSize; ++i) { + delete[] * (strs + i); + } + + Log.trace("OcrPack::recognize | raw : ", log_str_raw); + Log.trace("OcrPack::recognize | proc : ", log_str_proc); + return result; +} + +std::vector asst::OcrPack::recognize(const cv::Mat image, const asst::Rect& roi, const asst::TextRectProc& pred, bool without_det) +{ + auto rect_cor = [&roi, &pred, &without_det](TextRect& tr) -> bool { + if (without_det) { + tr.rect = roi; + } + else { + tr.rect.x += roi.x; + tr.rect.y += roi.y; + } + return pred(tr); + }; + Log.trace("OcrPack::recognize | roi : ", roi.to_string()); + return recognize(image(utils::make_rect(roi)), rect_cor, without_det); +} diff --git a/src/MeoAssistant/PenguinPack.cpp b/src/MeoAssistant/PenguinPack.cpp index 6831d64ccd..f5fc7bbfc8 100644 --- a/src/MeoAssistant/PenguinPack.cpp +++ b/src/MeoAssistant/PenguinPack.cpp @@ -1,95 +1,95 @@ -#include "PenguinPack.h" - -#include - -#include -#include -namespace penguin -{ -#include -} - -#include "AsstUtils.hpp" - -bool asst::PenguinPack::load(const std::string& dir) -{ - bool ret = load_json(dir + "/json/stages.json", dir + "/json/hash_index.json"); - - for (const auto& file : std::filesystem::directory_iterator(dir + "/items")) { - ret &= load_templ(file.path().stem().u8string(), file.path().u8string()); - } - return ret; -} - -void asst::PenguinPack::set_language(const std::string& server) -{ - m_language = server; - penguin::load_server(server.c_str()); -} - -std::string asst::PenguinPack::recognize(const cv::Mat image) -{ - std::vector buf; - cv::imencode(".png", image, buf); - return penguin::recognize(buf.data(), buf.size()); -} - -bool asst::PenguinPack::load_json(const std::string& stage_path, const std::string& hash_path) -{ - auto stage_parse_ret = json::parse(utils::load_file_without_bom(stage_path)); - if (!stage_parse_ret) { - m_last_error = stage_path + " parsing failed"; - return false; - } - // stage_json 来自 https://penguin-stats.io/PenguinStats/api/v2/stages - // 和接口需要的json有点区别,这里做个转换 - json::value stage_json = std::move(stage_parse_ret.value()); - json::object cvt_stage_json; - try { - for (const json::value& stage_info : stage_json.as_array()) { - if (!stage_info.exist("dropInfos")) { // 这种一般是以前的活动关,现在已经关闭了的 - continue; - } - std::string key = stage_info.at("code").as_string(); - json::value stage_dst; - stage_dst["stageId"] = stage_info.at("stageId"); - std::vector drops_vector; - for (const json::value& drop_info : stage_info.at("dropInfos").as_array()) { - if (drop_info.exist("itemId")) { - // 幸运掉落,家具啥的,企鹅数据不接,忽略掉 - if (drop_info.at("dropType").as_string() == "FURNITURE") { - continue; - } - drops_vector.emplace_back(drop_info.at("itemId")); - } - } - stage_dst["drops"] = json::array(std::move(drops_vector)); - stage_dst["existence"] = stage_info.at("existence").at(m_language).at("exist"); - - cvt_stage_json.emplace(std::move(key), std::move(stage_dst)); - } - } - catch (json::exception& e) { - m_last_error = stage_path + " parsing error " + e.what(); - return false; - } - std::string cvt_stage_string = cvt_stage_json.to_string(); - penguin::load_json(cvt_stage_string.c_str(), utils::load_file_without_bom(hash_path).c_str()); - return true; -} - -bool asst::PenguinPack::load_templ(const std::string& item_id, const std::string& path) -{ - cv::Mat image = cv::imread(path); - if (image.empty()) { - m_last_error = "templ is empty"; - return false; - } - - std::vector buf; - cv::imencode(".png", image, buf); - - penguin::load_templ(item_id.c_str(), buf.data(), buf.size()); - - return true; -} \ No newline at end of file +#include "PenguinPack.h" + +#include + +#include +#include +namespace penguin +{ +#include +} + +#include "AsstUtils.hpp" + +bool asst::PenguinPack::load(const std::string& dir) +{ + bool ret = load_json(dir + "/json/stages.json", dir + "/json/hash_index.json"); + + for (const auto& file : std::filesystem::directory_iterator(dir + "/items")) { + ret &= load_templ(file.path().stem().u8string(), file.path().u8string()); + } + return ret; +} + +void asst::PenguinPack::set_language(const std::string& server) +{ + m_language = server; + penguin::load_server(server.c_str()); +} + +std::string asst::PenguinPack::recognize(const cv::Mat image) +{ + std::vector buf; + cv::imencode(".png", image, buf); + return penguin::recognize(buf.data(), buf.size()); +} + +bool asst::PenguinPack::load_json(const std::string& stage_path, const std::string& hash_path) +{ + auto stage_parse_ret = json::parse(utils::load_file_without_bom(stage_path)); + if (!stage_parse_ret) { + m_last_error = stage_path + " parsing failed"; + return false; + } + // stage_json 来自 https://penguin-stats.io/PenguinStats/api/v2/stages + // 和接口需要的json有点区别,这里做个转换 + json::value stage_json = std::move(stage_parse_ret.value()); + json::object cvt_stage_json; + try { + for (const json::value& stage_info : stage_json.as_array()) { + if (!stage_info.exist("dropInfos")) { // 这种一般是以前的活动关,现在已经关闭了的 + continue; + } + std::string key = stage_info.at("code").as_string(); + json::value stage_dst; + stage_dst["stageId"] = stage_info.at("stageId"); + std::vector drops_vector; + for (const json::value& drop_info : stage_info.at("dropInfos").as_array()) { + if (drop_info.exist("itemId")) { + // 幸运掉落,家具啥的,企鹅数据不接,忽略掉 + if (drop_info.at("dropType").as_string() == "FURNITURE") { + continue; + } + drops_vector.emplace_back(drop_info.at("itemId")); + } + } + stage_dst["drops"] = json::array(std::move(drops_vector)); + stage_dst["existence"] = stage_info.at("existence").at(m_language).at("exist"); + + cvt_stage_json.emplace(std::move(key), std::move(stage_dst)); + } + } + catch (json::exception& e) { + m_last_error = stage_path + " parsing error " + e.what(); + return false; + } + std::string cvt_stage_string = cvt_stage_json.to_string(); + penguin::load_json(cvt_stage_string.c_str(), utils::load_file_without_bom(hash_path).c_str()); + return true; +} + +bool asst::PenguinPack::load_templ(const std::string& item_id, const std::string& path) +{ + cv::Mat image = cv::imread(path); + if (image.empty()) { + m_last_error = "templ is empty"; + return false; + } + + std::vector buf; + cv::imencode(".png", image, buf); + + penguin::load_templ(item_id.c_str(), buf.data(), buf.size()); + + return true; +} diff --git a/src/MeoAssistant/PenguinUploader.cpp b/src/MeoAssistant/PenguinUploader.cpp index c762f78b0f..cc81c92d7e 100644 --- a/src/MeoAssistant/PenguinUploader.cpp +++ b/src/MeoAssistant/PenguinUploader.cpp @@ -1,58 +1,58 @@ -#include "PenguinUploader.h" - -// #include - -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" -#include "Resource.h" -#include "Version.h" - -bool asst::PenguinUploader::upload(const std::string& rec_res) -{ - std::string body = cvt_json(rec_res); - return request_penguin(body); -} - -std::string asst::PenguinUploader::cvt_json(const std::string& rec_res) -{ - auto parse_ret = json::parse(rec_res); - if (!parse_ret) { - return std::string(); - } - json::value rec = std::move(parse_ret.value()); - - // Doc: https://developer.penguin-stats.io/public-api/api-v2-instruction/report-api - json::value body; - auto& opt = Resrc.cfg().get_options(); - body["server"] = opt.penguin_report.server; - body["stageId"] = rec["stage"]["stageId"]; - // To fix: https://github.com/MistEO/MeoAssistantArknights/issues/40 - body["drops"] = json::array(); - for (auto&& drop : rec["drops"].as_array()) { - if (!drop["itemId"].as_string().empty()) { - body["drops"].as_array().emplace_back(drop); - } - } - body["source"] = "MeoAssistant"; - body["version"] = Version; - - return body.to_string(); -} - -bool asst::PenguinUploader::request_penguin(const std::string& body) -{ - auto& opt = Resrc.cfg().get_options(); - std::string body_escape = utils::string_replace_all(body, "\"", "\\\""); - std::string cmd_line = utils::string_replace_all(opt.penguin_report.cmd_format, "[body]", body_escape); - cmd_line = utils::string_replace_all(cmd_line, "[extra]", opt.penguin_report.extra_param); - - Log.trace("request_penguin |", cmd_line); - - std::string response = utils::callcmd(cmd_line); - - Log.trace("response:\n", response); - - return true; -} +#include "PenguinUploader.h" + +// #include + +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" +#include "Resource.h" +#include "Version.h" + +bool asst::PenguinUploader::upload(const std::string& rec_res) +{ + std::string body = cvt_json(rec_res); + return request_penguin(body); +} + +std::string asst::PenguinUploader::cvt_json(const std::string& rec_res) +{ + auto parse_ret = json::parse(rec_res); + if (!parse_ret) { + return std::string(); + } + json::value rec = std::move(parse_ret.value()); + + // Doc: https://developer.penguin-stats.io/public-api/api-v2-instruction/report-api + json::value body; + auto& opt = Resrc.cfg().get_options(); + body["server"] = opt.penguin_report.server; + body["stageId"] = rec["stage"]["stageId"]; + // To fix: https://github.com/MistEO/MeoAssistantArknights/issues/40 + body["drops"] = json::array(); + for (auto&& drop : rec["drops"].as_array()) { + if (!drop["itemId"].as_string().empty()) { + body["drops"].as_array().emplace_back(drop); + } + } + body["source"] = "MeoAssistant"; + body["version"] = Version; + + return body.to_string(); +} + +bool asst::PenguinUploader::request_penguin(const std::string& body) +{ + auto& opt = Resrc.cfg().get_options(); + std::string body_escape = utils::string_replace_all(body, "\"", "\\\""); + std::string cmd_line = utils::string_replace_all(opt.penguin_report.cmd_format, "[body]", body_escape); + cmd_line = utils::string_replace_all(cmd_line, "[extra]", opt.penguin_report.extra_param); + + Log.trace("request_penguin |", cmd_line); + + std::string response = utils::callcmd(cmd_line); + + Log.trace("response:\n", response); + + return true; +} diff --git a/src/MeoAssistant/ProcessTask.cpp b/src/MeoAssistant/ProcessTask.cpp index a30369b848..420c529958 100644 --- a/src/MeoAssistant/ProcessTask.cpp +++ b/src/MeoAssistant/ProcessTask.cpp @@ -1,236 +1,238 @@ -#include "ProcessTask.h" - -#include -#include - -#include - -#include "AsstUtils.hpp" -#include "Controller.h" -#include "PenguinUploader.h" -#include "ProcessTaskImageAnalyzer.h" -#include "Resource.h" -#include "Logger.hpp" -#include "RuntimeStatus.h" - -using namespace asst; - -asst::ProcessTask::ProcessTask(const AbstractTask& abs, std::vector tasks_name) - : AbstractTask(abs), - m_cur_tasks_name(std::move(tasks_name)) -{} - -asst::ProcessTask::ProcessTask(AbstractTask&& abs, std::vector tasks_name) noexcept - : AbstractTask(std::move(abs)), - m_cur_tasks_name(std::move(tasks_name)) -{} - -bool ProcessTask::_run() -{ - json::value task_start_json = json::object{ - { "task_type", "ProcessTask" }, - { "task_chain", m_task_chain }, - { "tasks", json::array(m_cur_tasks_name) } - }; - m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); - - auto& task_delay = Resrc.cfg().get_options().task_delay; - while (!m_cur_tasks_name.empty()) { - if (need_exit()) { - return false; - } - Rect rect; - // 如果第一个任务是JustReturn的,那就没必要再截图并计算了 - if (auto front_task_ptr = Task.get(m_cur_tasks_name.front()); - front_task_ptr->algorithm == AlgorithmType::JustReturn) { - m_cur_task_ptr = front_task_ptr; - } - else { - const auto image = Ctrler.get_image(); - ProcessTaskImageAnalyzer analyzer(image, m_cur_tasks_name); - if (!analyzer.analyze()) { - return false; - } - m_cur_task_ptr = analyzer.get_result(); - rect = analyzer.get_rect(); - } - if (need_exit()) { - return false; - } - std::string cur_name = m_cur_task_ptr->name; - - const auto& res_move = m_cur_task_ptr->rect_move; - if (!res_move.empty()) { - rect.x += res_move.x; - rect.y += res_move.y; - rect.width = res_move.width; - rect.height = res_move.height; - } - - int& exec_times = m_exec_times[cur_name]; - - json::value callback_json = json::object{ - { "name", cur_name }, - { "type", static_cast(m_cur_task_ptr->action) }, - { "exec_times", exec_times }, - { "max_times", m_cur_task_ptr->max_times }, - { "task_type", "ProcessTask" }, - { "algorithm", static_cast(m_cur_task_ptr->algorithm) } - }; - m_callback(AsstMsg::TaskMatched, callback_json, m_callback_arg); - - int max_times = m_cur_task_ptr->max_times; - if (auto iter = m_times_limit.find(cur_name); - iter != m_times_limit.cend()) { - max_times = iter->second; - callback_json["times_limit"] = max_times; - } - - if (exec_times >= max_times) { - m_callback(AsstMsg::ReachedLimit, callback_json, m_callback_arg); - - json::value next_json = callback_json; - next_json["tasks"] = json::array(m_cur_task_ptr->exceeded_next); - next_json["retry_times"] = m_retry_times; - next_json["task_chain"] = m_task_chain; - //m_callback(AsstMsg::AppendProcessTask, next_json, m_callback_arg); - //return true; - - Log.trace(next_json.to_string()); - set_tasks(m_cur_task_ptr->exceeded_next); - sleep(task_delay); - continue; - } - - // 前置固定延时 - if (!sleep(m_cur_task_ptr->pre_delay)) { - return false; - } - - bool need_stop = false; - switch (m_cur_task_ptr->action) { - case ProcessTaskAction::ClickRect: - rect = m_cur_task_ptr->specific_rect; - [[fallthrough]]; - case ProcessTaskAction::ClickSelf: - exec_click_task(rect); - break; - case ProcessTaskAction::ClickRand: { - auto&& [width, height] = Ctrler.get_scale_size(); - const Rect full_rect(0, 0, width, height); - exec_click_task(full_rect); - } break; - case ProcessTaskAction::SwipeToTheLeft: - case ProcessTaskAction::SwipeToTheRight: - exec_swipe_task(m_cur_task_ptr->action); - break; - case ProcessTaskAction::DoNothing: - break; - case ProcessTaskAction::Stop: - m_callback(AsstMsg::ProcessTaskStopAction, json::object{ { "task_chain", m_task_chain } }, m_callback_arg); - need_stop = true; - break; - case ProcessTaskAction::StageDrops: { - exec_stage_drops(); - } break; - default: - break; - } - m_cur_retry = 0; - - ++exec_times; - - Status.set("Last" + cur_name, time(NULL)); - - // 减少其他任务的执行次数 - // 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效 - // 所以要给蓝色开始行动的次数减一 - for (const std::string& reduce : m_cur_task_ptr->reduce_other_times) { - --m_exec_times[reduce]; - } - - if (need_stop) { - return true; - } - - callback_json["exec_times"] = exec_times; - m_callback(AsstMsg::TaskCompleted, callback_json, m_callback_arg); - - // 后置固定延时 - int rear_delay = m_cur_task_ptr->rear_delay; - if (auto iter = m_rear_delay.find(cur_name); - iter != m_rear_delay.cend()) { - rear_delay = iter->second; - } - if (!sleep(rear_delay)) { - return false; - } - - json::value next_json = callback_json; - next_json["task_chain"] = m_task_chain; - next_json["retry_times"] = m_retry_times; - next_json["tasks"] = json::array(m_cur_task_ptr->next); - Log.trace(next_json.to_string()); - - set_tasks(m_cur_task_ptr->next); - sleep(task_delay); - } - - return true; -} - -void asst::ProcessTask::exec_stage_drops() -{ - cv::Mat image = Ctrler.get_image(true); - std::string res = Resrc.penguin().recognize(image); - m_callback(AsstMsg::StageDrops, json::parse(res).value(), m_callback_arg); - - if (m_rear_delay.find("StartButton2") == m_rear_delay.cend()) { - int64_t start_times = Status.get("LastStartButton2"); - if (start_times > 0) { - int64_t duration = time(NULL) - start_times; - int64_t delay = duration * 1000 - m_cur_task_ptr->rear_delay; - m_rear_delay["StartButton2"] = delay; - } - } - - auto& opt = Resrc.cfg().get_options(); - //if (opt.print_window) { - // //static const std::string dirname = utils::get_cur_dir() + "screenshot\\"; - // //save_image(image, dirname); - //} - if (opt.penguin_report.enable) { - PenguinUploader::upload(res); - } -} - -void ProcessTask::exec_click_task(const Rect& matched_rect) -{ - Ctrler.click(matched_rect); -} - -void asst::ProcessTask::exec_swipe_task(ProcessTaskAction action) -{ - const auto&& [width, height] = Ctrler.get_scale_size(); - - const static Rect right_rect(width * 0.8, - height * 0.4, - width * 0.1, - height * 0.2); - - const static Rect left_rect(width * 0.1, - height * 0.4, - width * 0.1, - height * 0.2); - - switch (action) { - case asst::ProcessTaskAction::SwipeToTheLeft: - Ctrler.swipe(left_rect, right_rect); - break; - case asst::ProcessTaskAction::SwipeToTheRight: - Ctrler.swipe(right_rect, left_rect); - break; - default: // 走不到这里,TODO 报个错 - break; - } -} +#include "ProcessTask.h" + +#include +#include + +#include + +#include "AsstUtils.hpp" +#include "Controller.h" +#include "PenguinUploader.h" +#include "ProcessTaskImageAnalyzer.h" +#include "Resource.h" +#include "Logger.hpp" +#include "RuntimeStatus.h" + +using namespace asst; + +asst::ProcessTask::ProcessTask(const AbstractTask& abs, std::vector tasks_name) + : AbstractTask(abs), + m_cur_tasks_name(std::move(tasks_name)) +{ +} + +asst::ProcessTask::ProcessTask(AbstractTask&& abs, std::vector tasks_name) noexcept + : AbstractTask(std::move(abs)), + m_cur_tasks_name(std::move(tasks_name)) +{ +} + +bool ProcessTask::_run() +{ + json::value task_start_json = json::object{ + { "task_type", "ProcessTask" }, + { "task_chain", m_task_chain }, + { "tasks", json::array(m_cur_tasks_name) } + }; + m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); + + auto& task_delay = Resrc.cfg().get_options().task_delay; + while (!m_cur_tasks_name.empty()) { + if (need_exit()) { + return false; + } + Rect rect; + // 如果第一个任务是JustReturn的,那就没必要再截图并计算了 + if (auto front_task_ptr = Task.get(m_cur_tasks_name.front()); + front_task_ptr->algorithm == AlgorithmType::JustReturn) { + m_cur_task_ptr = front_task_ptr; + } + else { + const auto image = Ctrler.get_image(); + ProcessTaskImageAnalyzer analyzer(image, m_cur_tasks_name); + if (!analyzer.analyze()) { + return false; + } + m_cur_task_ptr = analyzer.get_result(); + rect = analyzer.get_rect(); + } + if (need_exit()) { + return false; + } + std::string cur_name = m_cur_task_ptr->name; + + const auto& res_move = m_cur_task_ptr->rect_move; + if (!res_move.empty()) { + rect.x += res_move.x; + rect.y += res_move.y; + rect.width = res_move.width; + rect.height = res_move.height; + } + + int& exec_times = m_exec_times[cur_name]; + + json::value callback_json = json::object{ + { "name", cur_name }, + { "type", static_cast(m_cur_task_ptr->action) }, + { "exec_times", exec_times }, + { "max_times", m_cur_task_ptr->max_times }, + { "task_type", "ProcessTask" }, + { "algorithm", static_cast(m_cur_task_ptr->algorithm) } + }; + m_callback(AsstMsg::TaskMatched, callback_json, m_callback_arg); + + int max_times = m_cur_task_ptr->max_times; + if (auto iter = m_times_limit.find(cur_name); + iter != m_times_limit.cend()) { + max_times = iter->second; + callback_json["times_limit"] = max_times; + } + + if (exec_times >= max_times) { + m_callback(AsstMsg::ReachedLimit, callback_json, m_callback_arg); + + json::value next_json = callback_json; + next_json["tasks"] = json::array(m_cur_task_ptr->exceeded_next); + next_json["retry_times"] = m_retry_times; + next_json["task_chain"] = m_task_chain; + //m_callback(AsstMsg::AppendProcessTask, next_json, m_callback_arg); + //return true; + + Log.trace(next_json.to_string()); + set_tasks(m_cur_task_ptr->exceeded_next); + sleep(task_delay); + continue; + } + + // 前置固定延时 + if (!sleep(m_cur_task_ptr->pre_delay)) { + return false; + } + + bool need_stop = false; + switch (m_cur_task_ptr->action) { + case ProcessTaskAction::ClickRect: + rect = m_cur_task_ptr->specific_rect; + [[fallthrough]]; + case ProcessTaskAction::ClickSelf: + exec_click_task(rect); + break; + case ProcessTaskAction::ClickRand: { + auto&& [width, height] = Ctrler.get_scale_size(); + const Rect full_rect(0, 0, width, height); + exec_click_task(full_rect); + } break; + case ProcessTaskAction::SwipeToTheLeft: + case ProcessTaskAction::SwipeToTheRight: + exec_swipe_task(m_cur_task_ptr->action); + break; + case ProcessTaskAction::DoNothing: + break; + case ProcessTaskAction::Stop: + m_callback(AsstMsg::ProcessTaskStopAction, json::object{ { "task_chain", m_task_chain } }, m_callback_arg); + need_stop = true; + break; + case ProcessTaskAction::StageDrops: { + exec_stage_drops(); + } break; + default: + break; + } + m_cur_retry = 0; + + ++exec_times; + + Status.set("Last" + cur_name, time(NULL)); + + // 减少其他任务的执行次数 + // 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效 + // 所以要给蓝色开始行动的次数减一 + for (const std::string& reduce : m_cur_task_ptr->reduce_other_times) { + --m_exec_times[reduce]; + } + + if (need_stop) { + return true; + } + + callback_json["exec_times"] = exec_times; + m_callback(AsstMsg::TaskCompleted, callback_json, m_callback_arg); + + // 后置固定延时 + int rear_delay = m_cur_task_ptr->rear_delay; + if (auto iter = m_rear_delay.find(cur_name); + iter != m_rear_delay.cend()) { + rear_delay = iter->second; + } + if (!sleep(rear_delay)) { + return false; + } + + json::value next_json = callback_json; + next_json["task_chain"] = m_task_chain; + next_json["retry_times"] = m_retry_times; + next_json["tasks"] = json::array(m_cur_task_ptr->next); + Log.trace(next_json.to_string()); + + set_tasks(m_cur_task_ptr->next); + sleep(task_delay); + } + + return true; +} + +void asst::ProcessTask::exec_stage_drops() +{ + cv::Mat image = Ctrler.get_image(true); + std::string res = Resrc.penguin().recognize(image); + m_callback(AsstMsg::StageDrops, json::parse(res).value(), m_callback_arg); + + if (m_rear_delay.find("StartButton2") == m_rear_delay.cend()) { + int64_t start_times = Status.get("LastStartButton2"); + if (start_times > 0) { + int64_t duration = time(NULL) - start_times; + int64_t delay = duration * 1000 - m_cur_task_ptr->rear_delay; + m_rear_delay["StartButton2"] = delay; + } + } + + auto& opt = Resrc.cfg().get_options(); + //if (opt.print_window) { + // //static const std::string dirname = utils::get_cur_dir() + "screenshot\\"; + // //save_image(image, dirname); + //} + if (opt.penguin_report.enable) { + PenguinUploader::upload(res); + } +} + +void ProcessTask::exec_click_task(const Rect& matched_rect) +{ + Ctrler.click(matched_rect); +} + +void asst::ProcessTask::exec_swipe_task(ProcessTaskAction action) +{ + const auto&& [width, height] = Ctrler.get_scale_size(); + + const static Rect right_rect(width * 0.8, + height * 0.4, + width * 0.1, + height * 0.2); + + const static Rect left_rect(width * 0.1, + height * 0.4, + width * 0.1, + height * 0.2); + + switch (action) { + case asst::ProcessTaskAction::SwipeToTheLeft: + Ctrler.swipe(left_rect, right_rect); + break; + case asst::ProcessTaskAction::SwipeToTheRight: + Ctrler.swipe(right_rect, left_rect); + break; + default: // 走不到这里,TODO 报个错 + break; + } +} diff --git a/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp b/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp index eea31a1b8e..8dabfe4112 100644 --- a/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp +++ b/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp @@ -1,126 +1,126 @@ -#include "ProcessTaskImageAnalyzer.h" - -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" -#include "MatchImageAnalyzer.h" -#include "OcrImageAnalyzer.h" -#include "Resource.h" - -asst::ProcessTaskImageAnalyzer::ProcessTaskImageAnalyzer(const cv::Mat image, std::vector tasks_name) - : AbstractImageAnalyzer(image), - m_tasks_name(std::move(tasks_name)) -{ - ; -} - -asst::ProcessTaskImageAnalyzer::~ProcessTaskImageAnalyzer() = default; - -bool asst::ProcessTaskImageAnalyzer::match_analyze(std::shared_ptr task_ptr) -{ - if (!m_match_analyzer) { - m_match_analyzer = std::make_unique(m_image); - } - const auto match_task_ptr = std::dynamic_pointer_cast(task_ptr); - m_match_analyzer->set_task_info(*match_task_ptr); - - if (m_match_analyzer->analyze()) { - m_result = match_task_ptr; - m_result_rect = m_match_analyzer->get_result().rect; - task_ptr->region_of_appeared = m_result_rect; - return true; - } - return false; -} - -bool asst::ProcessTaskImageAnalyzer::ocr_analyze(std::shared_ptr task_ptr) -{ - std::shared_ptr ocr_task_ptr = std::dynamic_pointer_cast(task_ptr); - - // 先尝试从缓存的结果里找 - //for (const TextRect& tr : m_ocr_cache) { - // TextRect temp = tr; - // for (const auto& [regex, new_str] : ocr_task_ptr->replace_map) { - // temp.text = std::regex_replace(temp.text, std::regex(regex), new_str); - // } - // for (const auto& text : ocr_task_ptr->text) { - // bool flag = false; - // if (ocr_task_ptr->need_full_match) { - // if (temp.text == text) { - // flag = true; - // } - // } - // else if (temp.text.find(text) != std::string::npos) { - // flag = true; - // } - // // 即使找到了,也得在ROI里才行,不然过滤掉 - // if (flag && ocr_task_ptr->roi.include(tr.rect)) { - // m_result = ocr_task_ptr; - // m_result_rect = tr.rect; - // ocr_task_ptr->region_of_appeared = m_result_rect; - // Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found in cache", tr.to_string()); - // return true; - // } - // } - //} - if (!m_ocr_analyzer) { - m_ocr_analyzer = std::make_unique(m_image); - } - m_ocr_analyzer->set_task_info(*ocr_task_ptr); - - bool ret = m_ocr_analyzer->analyze(); - - if (ret) { - const auto& ocr_result = m_ocr_analyzer->get_result(); - auto& res = ocr_result.front(); - m_result = ocr_task_ptr; - m_result_rect = res.rect; - ocr_task_ptr->region_of_appeared = res.rect; - //m_ocr_cache.insert(m_ocr_cache.end(), ocr_result.begin(), ocr_result.end()); - Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found", res.to_string()); - } - return ret; -} - -void asst::ProcessTaskImageAnalyzer::reset() noexcept -{ - //m_ocr_cache.clear(); - m_ocr_analyzer = nullptr; - m_match_analyzer = nullptr; -} - -bool asst::ProcessTaskImageAnalyzer::analyze() -{ - m_result = nullptr; - m_result_rect = Rect(); - - for (const std::string& task_name : m_tasks_name) { - auto task_ptr = Task.get(task_name); - - switch (task_ptr->algorithm) { - case AlgorithmType::JustReturn: - m_result = task_ptr; - return true; - case AlgorithmType::MatchTemplate: - if (match_analyze(task_ptr)) { - return true; - } - break; - case AlgorithmType::OcrDetect: - if (ocr_analyze(task_ptr)) { - return true; - } - break; - default: - break; - } - } - return false; -} - -void asst::ProcessTaskImageAnalyzer::set_image(const cv::Mat image) -{ - AbstractImageAnalyzer::set_image(image); - reset(); -} \ No newline at end of file +#include "ProcessTaskImageAnalyzer.h" + +#include + +#include "AsstUtils.hpp" +#include "Logger.hpp" +#include "MatchImageAnalyzer.h" +#include "OcrImageAnalyzer.h" +#include "Resource.h" + +asst::ProcessTaskImageAnalyzer::ProcessTaskImageAnalyzer(const cv::Mat image, std::vector tasks_name) + : AbstractImageAnalyzer(image), + m_tasks_name(std::move(tasks_name)) +{ + ; +} + +asst::ProcessTaskImageAnalyzer::~ProcessTaskImageAnalyzer() = default; + +bool asst::ProcessTaskImageAnalyzer::match_analyze(std::shared_ptr task_ptr) +{ + if (!m_match_analyzer) { + m_match_analyzer = std::make_unique(m_image); + } + const auto match_task_ptr = std::dynamic_pointer_cast(task_ptr); + m_match_analyzer->set_task_info(*match_task_ptr); + + if (m_match_analyzer->analyze()) { + m_result = match_task_ptr; + m_result_rect = m_match_analyzer->get_result().rect; + task_ptr->region_of_appeared = m_result_rect; + return true; + } + return false; +} + +bool asst::ProcessTaskImageAnalyzer::ocr_analyze(std::shared_ptr task_ptr) +{ + std::shared_ptr ocr_task_ptr = std::dynamic_pointer_cast(task_ptr); + + // 先尝试从缓存的结果里找 + //for (const TextRect& tr : m_ocr_cache) { + // TextRect temp = tr; + // for (const auto& [regex, new_str] : ocr_task_ptr->replace_map) { + // temp.text = std::regex_replace(temp.text, std::regex(regex), new_str); + // } + // for (const auto& text : ocr_task_ptr->text) { + // bool flag = false; + // if (ocr_task_ptr->need_full_match) { + // if (temp.text == text) { + // flag = true; + // } + // } + // else if (temp.text.find(text) != std::string::npos) { + // flag = true; + // } + // // 即使找到了,也得在ROI里才行,不然过滤掉 + // if (flag && ocr_task_ptr->roi.include(tr.rect)) { + // m_result = ocr_task_ptr; + // m_result_rect = tr.rect; + // ocr_task_ptr->region_of_appeared = m_result_rect; + // Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found in cache", tr.to_string()); + // return true; + // } + // } + //} + if (!m_ocr_analyzer) { + m_ocr_analyzer = std::make_unique(m_image); + } + m_ocr_analyzer->set_task_info(*ocr_task_ptr); + + bool ret = m_ocr_analyzer->analyze(); + + if (ret) { + const auto& ocr_result = m_ocr_analyzer->get_result(); + auto& res = ocr_result.front(); + m_result = ocr_task_ptr; + m_result_rect = res.rect; + ocr_task_ptr->region_of_appeared = res.rect; + //m_ocr_cache.insert(m_ocr_cache.end(), ocr_result.begin(), ocr_result.end()); + Log.trace("ProcessTaskImageAnalyzer::ocr_analyze | found", res.to_string()); + } + return ret; +} + +void asst::ProcessTaskImageAnalyzer::reset() noexcept +{ + //m_ocr_cache.clear(); + m_ocr_analyzer = nullptr; + m_match_analyzer = nullptr; +} + +bool asst::ProcessTaskImageAnalyzer::analyze() +{ + m_result = nullptr; + m_result_rect = Rect(); + + for (const std::string& task_name : m_tasks_name) { + auto task_ptr = Task.get(task_name); + + switch (task_ptr->algorithm) { + case AlgorithmType::JustReturn: + m_result = task_ptr; + return true; + case AlgorithmType::MatchTemplate: + if (match_analyze(task_ptr)) { + return true; + } + break; + case AlgorithmType::OcrDetect: + if (ocr_analyze(task_ptr)) { + return true; + } + break; + default: + break; + } + } + return false; +} + +void asst::ProcessTaskImageAnalyzer::set_image(const cv::Mat image) +{ + AbstractImageAnalyzer::set_image(image); + reset(); +} diff --git a/src/MeoAssistant/ProcessTaskImageAnalyzer.h b/src/MeoAssistant/ProcessTaskImageAnalyzer.h index c87161030e..8e8ff7826f 100644 --- a/src/MeoAssistant/ProcessTaskImageAnalyzer.h +++ b/src/MeoAssistant/ProcessTaskImageAnalyzer.h @@ -1,63 +1,63 @@ -#pragma once -#include "AbstractImageAnalyzer.h" - -#include -#include -#include - -#include "AsstDef.h" - -namespace asst -{ - class OcrImageAnalyzer; - class MatchImageAnalyzer; - - class ProcessTaskImageAnalyzer final : public AbstractImageAnalyzer - { - public: - using AbstractImageAnalyzer::AbstractImageAnalyzer; - ProcessTaskImageAnalyzer(const cv::Mat image, const Rect& roi) = delete; - ProcessTaskImageAnalyzer(const cv::Mat image, std::vector tasks_name); - virtual ~ProcessTaskImageAnalyzer(); - - virtual bool analyze() override; - virtual void set_image(const cv::Mat image) override; - - void set_tasks(std::vector tasks_name) - { - m_tasks_name = std::move(tasks_name); - } - std::shared_ptr get_result() const noexcept - { - return m_result; - } - const Rect& get_rect() const noexcept - { - return m_result_rect; - } - - ProcessTaskImageAnalyzer& operator=(const ProcessTaskImageAnalyzer&) = delete; - ProcessTaskImageAnalyzer& operator=(ProcessTaskImageAnalyzer&&) = delete; - - private: - // 该分析器不支持外部设置ROI - virtual void set_roi(const Rect& roi) noexcept override - { - AbstractImageAnalyzer::set_roi(roi); - } - virtual void set_image(const cv::Mat image, const Rect& roi) - { - AbstractImageAnalyzer::set_image(image, roi); - } - bool match_analyze(std::shared_ptr task_ptr); - bool ocr_analyze(std::shared_ptr task_ptr); - void reset() noexcept; - - std::unique_ptr m_ocr_analyzer; - std::unique_ptr m_match_analyzer; - std::vector m_tasks_name; - std::shared_ptr m_result; - Rect m_result_rect; - //std::vector m_ocr_cache; - }; -} +#pragma once +#include "AbstractImageAnalyzer.h" + +#include +#include +#include + +#include "AsstDef.h" + +namespace asst +{ + class OcrImageAnalyzer; + class MatchImageAnalyzer; + + class ProcessTaskImageAnalyzer final : public AbstractImageAnalyzer + { + public: + using AbstractImageAnalyzer::AbstractImageAnalyzer; + ProcessTaskImageAnalyzer(const cv::Mat image, const Rect& roi) = delete; + ProcessTaskImageAnalyzer(const cv::Mat image, std::vector tasks_name); + virtual ~ProcessTaskImageAnalyzer(); + + virtual bool analyze() override; + virtual void set_image(const cv::Mat image) override; + + void set_tasks(std::vector tasks_name) + { + m_tasks_name = std::move(tasks_name); + } + std::shared_ptr get_result() const noexcept + { + return m_result; + } + const Rect& get_rect() const noexcept + { + return m_result_rect; + } + + ProcessTaskImageAnalyzer& operator=(const ProcessTaskImageAnalyzer&) = delete; + ProcessTaskImageAnalyzer& operator=(ProcessTaskImageAnalyzer&&) = delete; + + private: + // 该分析器不支持外部设置ROI + virtual void set_roi(const Rect& roi) noexcept override + { + AbstractImageAnalyzer::set_roi(roi); + } + virtual void set_image(const cv::Mat image, const Rect& roi) + { + AbstractImageAnalyzer::set_image(image, roi); + } + bool match_analyze(std::shared_ptr task_ptr); + bool ocr_analyze(std::shared_ptr task_ptr); + void reset() noexcept; + + std::unique_ptr m_ocr_analyzer; + std::unique_ptr m_match_analyzer; + std::vector m_tasks_name; + std::shared_ptr m_result; + Rect m_result_rect; + //std::vector m_ocr_cache; + }; +} diff --git a/src/MeoAssistant/RecruitConfiger.cpp b/src/MeoAssistant/RecruitConfiger.cpp index 30fcae6167..19a9a27e0b 100644 --- a/src/MeoAssistant/RecruitConfiger.cpp +++ b/src/MeoAssistant/RecruitConfiger.cpp @@ -34,4 +34,4 @@ bool asst::RecruitConfiger::parse(const json::value& json) }); return true; -} \ No newline at end of file +} diff --git a/src/MeoAssistant/RecruitImageAnalyzer.cpp b/src/MeoAssistant/RecruitImageAnalyzer.cpp index 845b9b55ea..3de89aa36d 100644 --- a/src/MeoAssistant/RecruitImageAnalyzer.cpp +++ b/src/MeoAssistant/RecruitImageAnalyzer.cpp @@ -94,4 +94,4 @@ bool asst::RecruitImageAnalyzer::refresh_analyze() } return false; -} \ No newline at end of file +} diff --git a/src/MeoAssistant/RecruitTask.cpp b/src/MeoAssistant/RecruitTask.cpp index 1a9aaae0ec..5ce071f49f 100644 --- a/src/MeoAssistant/RecruitTask.cpp +++ b/src/MeoAssistant/RecruitTask.cpp @@ -1,228 +1,228 @@ -#include "RecruitTask.h" - -#include -#include - -#include "Controller.h" -#include "RecruitImageAnalyzer.h" -#include "Resource.h" -#include "ProcessTask.h" - -using namespace asst; - -bool RecruitTask::_run() -{ - json::value task_start_json = json::object{ - { "task_type", "RecruitTask_run" }, - { "task_chain", m_task_chain }, - }; - m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); - - m_maybe_level = 0; - m_has_special_tag = false; - m_has_refresh = false; - - const cv::Mat image = Ctrler.get_image(); - if (image.empty()) { - m_callback(AsstMsg::ImageIsEmpty, task_start_json, m_callback_arg); - return false; - } - - RecruitImageAnalyzer analyzer(image); - - if (!analyzer.analyze()) { - return false; - } - m_has_refresh = !analyzer.get_refresh_rect().empty(); - - if (m_set_time) { - for (const auto& rect : analyzer.get_set_time_rect()) { - Ctrler.click(rect, false); - } - } - const std::vector& all_tags = analyzer.get_tags_result(); - - std::unordered_set all_tags_name; - std::vector all_tags_json_vector; - for (const TextRect& text_area : all_tags) { - all_tags_name.emplace(text_area.text); - all_tags_json_vector.emplace_back(text_area.text); - } - json::value all_tags_json; - all_tags_json["tags"] = json::array(all_tags_json_vector); - all_tags_json["task_chain"] = m_task_chain; - - /* 过滤tags数量不足的情况(可能是识别漏了) */ - if (all_tags.size() != RecruitConfiger::CorrectNumberOfTags) { - //all_tags_json["type"] = "OpenRecruit"; - //m_callback(AsstMsg::OcrResultError, all_tags_json, m_callback_arg); - return false; - } - - m_callback(AsstMsg::RecruitTagsDetected, all_tags_json, m_callback_arg); - - /* 针对特殊Tag的额外回调消息 */ - static const std::string SeniorOper = "高级资深干员"; - static const std::string SupportMachine = "支援机械"; - const std::vector SpecialTags = { SeniorOper, SupportMachine }; - - auto special_iter = std::find_first_of(SpecialTags.cbegin(), SpecialTags.cend(), all_tags_name.cbegin(), all_tags_name.cend()); - if (special_iter != SpecialTags.cend()) { - json::value special_tag_json; - special_tag_json["tag"] = *special_iter; - m_callback(AsstMsg::RecruitSpecialTag, special_tag_json, m_callback_arg); - m_has_special_tag = true; - } - - // 识别到的5个Tags,全组合排列 - std::vector> all_combs; - size_t len = all_tags.size(); - int count = (std::pow)(2, len); - for (int i = 0; i < count; ++i) { - std::vector temp; - for (int j = 0, mask = 1; j < len; ++j) { - if ((i & mask) != 0) { // What the fuck??? - temp.emplace_back(all_tags.at(j).text); - } - mask = mask * 2; - } - // 游戏里最多选择3个tag - if (!temp.empty() && temp.size() <= 3) { - all_combs.emplace_back(std::move(temp)); - } - } - - std::vector result_vec; - for (const std::vector& comb : all_combs) { - RecruitCombs oper_combs; - oper_combs.tags = comb; - - for (const RecruitOperInfo& cur_oper : Resrc.recruit().get_all_opers()) { - int matched_count = 0; - for (const std::string& tag : comb) { - if (cur_oper.tags.find(tag) != cur_oper.tags.cend()) { - ++matched_count; - } - else { - break; - } - } - - // 单个tags comb中的每一个tag,这个干员都有,才算该干员符合这个tags comb - if (matched_count != comb.size()) { - continue; - } - - if (cur_oper.level == 6) { - // 高资tag和六星强绑定,如果没有高资tag,即使其他tag匹配上了也不可能出六星 - if (std::find(comb.cbegin(), comb.cend(), SeniorOper) == comb.cend()) { - continue; - } - } - - oper_combs.opers.emplace_back(cur_oper); - - if (cur_oper.level == 1 || cur_oper.level == 2) { - if (oper_combs.min_level == 0) - oper_combs.min_level = cur_oper.level; - if (oper_combs.max_level == 0) - oper_combs.max_level = cur_oper.level; - // 一星、二星干员不计入最低等级,因为拉满9小时之后不可能出1、2星 - continue; - } - if (oper_combs.min_level == 0 || oper_combs.min_level > cur_oper.level) { - oper_combs.min_level = cur_oper.level; - } - if (oper_combs.max_level == 0 || oper_combs.max_level < cur_oper.level) { - oper_combs.max_level = cur_oper.level; - } - oper_combs.avg_level += cur_oper.level; - } - if (!oper_combs.opers.empty()) { - oper_combs.avg_level /= oper_combs.opers.size(); - result_vec.emplace_back(std::move(oper_combs)); - } - } - - std::sort(result_vec.begin(), result_vec.end(), - [](const RecruitCombs& lhs, const RecruitCombs& rhs) -> bool { - // 最小等级大的,排前面 - if (lhs.min_level != rhs.min_level) { - return lhs.min_level > rhs.min_level; - } - // 最大等级大的,排前面 - else if (lhs.max_level != rhs.max_level) { - return lhs.max_level > rhs.max_level; - } - // 平均等级高的,排前面 - else if (std::fabs(lhs.avg_level - rhs.avg_level) > DoubleDiff) { - return lhs.avg_level > rhs.avg_level; - } - // Tag数量少的,排前面 - else { - return lhs.tags.size() < rhs.tags.size(); - } - }); - - if (!result_vec.empty()) { - m_maybe_level = result_vec.at(0).min_level; - } - else { - m_maybe_level = 0; - } - - /* 整理识别结果Json */ - json::value results_json; - results_json["result"] = json::array(); - results_json["maybe_level"] = m_maybe_level; - results_json["task_chain"] = m_task_chain; - - std::vector result_json_vector; - for (const auto& comb : result_vec) { - auto& tags = comb.tags; - json::value comb_json; - - std::vector tags_json_vector; - for (const std::string& tag : tags) { - tags_json_vector.emplace_back(tag); - } - comb_json["tags"] = json::array(std::move(tags_json_vector)); - - std::vector opers_json_vector; - for (const RecruitOperInfo& oper_info : comb.opers) { - json::value oper_json; - oper_json["name"] = oper_info.name; - oper_json["level"] = oper_info.level; - opers_json_vector.emplace_back(std::move(oper_json)); - } - comb_json["opers"] = json::array(std::move(opers_json_vector)); - comb_json["tag_level"] = comb.min_level; - results_json["result"].as_array().emplace_back(std::move(comb_json)); - } - m_callback(AsstMsg::RecruitResult, results_json, m_callback_arg); - - if (!result_vec.empty()) { - /* 点击最优解的tags */ - if (std::find(m_select_level.cbegin(), m_select_level.cend(), m_maybe_level) != m_select_level.cend()) { - const std::vector& final_tags_name = result_vec.at(0).tags; - - for (const TextRect& text_area : all_tags) { - if (std::find(final_tags_name.cbegin(), final_tags_name.cend(), text_area.text) != final_tags_name.cend()) { - Ctrler.click(text_area.rect, true); - } - } - - json::value selected_json; - selected_json["tags"] = json::array(final_tags_name); - m_callback(AsstMsg::RecruitSelected, selected_json, m_callback_arg); - } - } - - return true; -} - -void RecruitTask::set_param(std::vector select_level, bool set_time) noexcept -{ - m_select_level = std::move(select_level); - m_set_time = set_time; -} +#include "RecruitTask.h" + +#include +#include + +#include "Controller.h" +#include "RecruitImageAnalyzer.h" +#include "Resource.h" +#include "ProcessTask.h" + +using namespace asst; + +bool RecruitTask::_run() +{ + json::value task_start_json = json::object{ + { "task_type", "RecruitTask_run" }, + { "task_chain", m_task_chain }, + }; + m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); + + m_maybe_level = 0; + m_has_special_tag = false; + m_has_refresh = false; + + const cv::Mat image = Ctrler.get_image(); + if (image.empty()) { + m_callback(AsstMsg::ImageIsEmpty, task_start_json, m_callback_arg); + return false; + } + + RecruitImageAnalyzer analyzer(image); + + if (!analyzer.analyze()) { + return false; + } + m_has_refresh = !analyzer.get_refresh_rect().empty(); + + if (m_set_time) { + for (const auto& rect : analyzer.get_set_time_rect()) { + Ctrler.click(rect, false); + } + } + const std::vector& all_tags = analyzer.get_tags_result(); + + std::unordered_set all_tags_name; + std::vector all_tags_json_vector; + for (const TextRect& text_area : all_tags) { + all_tags_name.emplace(text_area.text); + all_tags_json_vector.emplace_back(text_area.text); + } + json::value all_tags_json; + all_tags_json["tags"] = json::array(all_tags_json_vector); + all_tags_json["task_chain"] = m_task_chain; + + /* 过滤tags数量不足的情况(可能是识别漏了) */ + if (all_tags.size() != RecruitConfiger::CorrectNumberOfTags) { + //all_tags_json["type"] = "OpenRecruit"; + //m_callback(AsstMsg::OcrResultError, all_tags_json, m_callback_arg); + return false; + } + + m_callback(AsstMsg::RecruitTagsDetected, all_tags_json, m_callback_arg); + + /* 针对特殊Tag的额外回调消息 */ + static const std::string SeniorOper = "高级资深干员"; + static const std::string SupportMachine = "支援机械"; + const std::vector SpecialTags = { SeniorOper, SupportMachine }; + + auto special_iter = std::find_first_of(SpecialTags.cbegin(), SpecialTags.cend(), all_tags_name.cbegin(), all_tags_name.cend()); + if (special_iter != SpecialTags.cend()) { + json::value special_tag_json; + special_tag_json["tag"] = *special_iter; + m_callback(AsstMsg::RecruitSpecialTag, special_tag_json, m_callback_arg); + m_has_special_tag = true; + } + + // 识别到的5个Tags,全组合排列 + std::vector> all_combs; + size_t len = all_tags.size(); + int count = (std::pow)(2, len); + for (int i = 0; i < count; ++i) { + std::vector temp; + for (int j = 0, mask = 1; j < len; ++j) { + if ((i & mask) != 0) { // What the fuck??? + temp.emplace_back(all_tags.at(j).text); + } + mask = mask * 2; + } + // 游戏里最多选择3个tag + if (!temp.empty() && temp.size() <= 3) { + all_combs.emplace_back(std::move(temp)); + } + } + + std::vector result_vec; + for (const std::vector& comb : all_combs) { + RecruitCombs oper_combs; + oper_combs.tags = comb; + + for (const RecruitOperInfo& cur_oper : Resrc.recruit().get_all_opers()) { + int matched_count = 0; + for (const std::string& tag : comb) { + if (cur_oper.tags.find(tag) != cur_oper.tags.cend()) { + ++matched_count; + } + else { + break; + } + } + + // 单个tags comb中的每一个tag,这个干员都有,才算该干员符合这个tags comb + if (matched_count != comb.size()) { + continue; + } + + if (cur_oper.level == 6) { + // 高资tag和六星强绑定,如果没有高资tag,即使其他tag匹配上了也不可能出六星 + if (std::find(comb.cbegin(), comb.cend(), SeniorOper) == comb.cend()) { + continue; + } + } + + oper_combs.opers.emplace_back(cur_oper); + + if (cur_oper.level == 1 || cur_oper.level == 2) { + if (oper_combs.min_level == 0) + oper_combs.min_level = cur_oper.level; + if (oper_combs.max_level == 0) + oper_combs.max_level = cur_oper.level; + // 一星、二星干员不计入最低等级,因为拉满9小时之后不可能出1、2星 + continue; + } + if (oper_combs.min_level == 0 || oper_combs.min_level > cur_oper.level) { + oper_combs.min_level = cur_oper.level; + } + if (oper_combs.max_level == 0 || oper_combs.max_level < cur_oper.level) { + oper_combs.max_level = cur_oper.level; + } + oper_combs.avg_level += cur_oper.level; + } + if (!oper_combs.opers.empty()) { + oper_combs.avg_level /= oper_combs.opers.size(); + result_vec.emplace_back(std::move(oper_combs)); + } + } + + std::sort(result_vec.begin(), result_vec.end(), + [](const RecruitCombs& lhs, const RecruitCombs& rhs) -> bool { + // 最小等级大的,排前面 + if (lhs.min_level != rhs.min_level) { + return lhs.min_level > rhs.min_level; + } + // 最大等级大的,排前面 + else if (lhs.max_level != rhs.max_level) { + return lhs.max_level > rhs.max_level; + } + // 平均等级高的,排前面 + else if (std::fabs(lhs.avg_level - rhs.avg_level) > DoubleDiff) { + return lhs.avg_level > rhs.avg_level; + } + // Tag数量少的,排前面 + else { + return lhs.tags.size() < rhs.tags.size(); + } + }); + + if (!result_vec.empty()) { + m_maybe_level = result_vec.at(0).min_level; + } + else { + m_maybe_level = 0; + } + + /* 整理识别结果Json */ + json::value results_json; + results_json["result"] = json::array(); + results_json["maybe_level"] = m_maybe_level; + results_json["task_chain"] = m_task_chain; + + std::vector result_json_vector; + for (const auto& comb : result_vec) { + auto& tags = comb.tags; + json::value comb_json; + + std::vector tags_json_vector; + for (const std::string& tag : tags) { + tags_json_vector.emplace_back(tag); + } + comb_json["tags"] = json::array(std::move(tags_json_vector)); + + std::vector opers_json_vector; + for (const RecruitOperInfo& oper_info : comb.opers) { + json::value oper_json; + oper_json["name"] = oper_info.name; + oper_json["level"] = oper_info.level; + opers_json_vector.emplace_back(std::move(oper_json)); + } + comb_json["opers"] = json::array(std::move(opers_json_vector)); + comb_json["tag_level"] = comb.min_level; + results_json["result"].as_array().emplace_back(std::move(comb_json)); + } + m_callback(AsstMsg::RecruitResult, results_json, m_callback_arg); + + if (!result_vec.empty()) { + /* 点击最优解的tags */ + if (std::find(m_select_level.cbegin(), m_select_level.cend(), m_maybe_level) != m_select_level.cend()) { + const std::vector& final_tags_name = result_vec.at(0).tags; + + for (const TextRect& text_area : all_tags) { + if (std::find(final_tags_name.cbegin(), final_tags_name.cend(), text_area.text) != final_tags_name.cend()) { + Ctrler.click(text_area.rect, true); + } + } + + json::value selected_json; + selected_json["tags"] = json::array(final_tags_name); + m_callback(AsstMsg::RecruitSelected, selected_json, m_callback_arg); + } + } + + return true; +} + +void RecruitTask::set_param(std::vector select_level, bool set_time) noexcept +{ + m_select_level = std::move(select_level); + m_set_time = set_time; +} diff --git a/src/MeoAssistant/Resource.cpp b/src/MeoAssistant/Resource.cpp index 1fb66763de..1e82390dbc 100644 --- a/src/MeoAssistant/Resource.cpp +++ b/src/MeoAssistant/Resource.cpp @@ -1,81 +1,81 @@ -#include "Resource.h" - -#include -#include -#include - -#include - -#include "AsstDef.h" - -bool asst::Resource::load(const std::string& dir) -{ - constexpr static const char* TemplsFilename = "template"; - constexpr static const char* GeneralCfgFilename = "config.json"; - constexpr static const char* TaskDataFilename = "tasks.json"; - constexpr static const char* RecruitCfgFilename = "recruit.json"; - constexpr static const char* ItemCfgFilename = "item_index.json"; - constexpr static const char* InfrastCfgFilename = "infrast.json"; - constexpr static const char* UserCfgFilename = "..\\user.json"; - constexpr static const char* OcrResourceFilename = "PaddleOCR"; - constexpr static const char* PenguinResourceFilename = "penguin-stats-recognize"; - - /* 加载各个Json配置文件 */ - if (!m_general_cfg_unique_ins.load(dir + GeneralCfgFilename)) { - m_last_error = std::string(GeneralCfgFilename) + ": " + m_general_cfg_unique_ins.get_last_error(); - return false; - } - if (!Task.load(dir + TaskDataFilename)) { - m_last_error = std::string(TaskDataFilename) + ": " + Task.get_last_error(); - return false; - } - - if (!m_recruit_cfg_unique_ins.load(dir + RecruitCfgFilename)) { - m_last_error = std::string(RecruitCfgFilename) + ": " + m_recruit_cfg_unique_ins.get_last_error(); - return false; - } - if (!m_item_cfg_unique_ins.load(dir + ItemCfgFilename)) { - m_last_error = std::string(ItemCfgFilename) + ": " + m_item_cfg_unique_ins.get_last_error(); - return false; - } - if (!m_infrast_cfg_unique_ins.load(dir + InfrastCfgFilename)) { - m_last_error = std::string(InfrastCfgFilename) + ": " + m_infrast_cfg_unique_ins.get_last_error(); - return false; - } - - if (!m_user_cfg_unique_ins.load(dir + UserCfgFilename)) { - m_last_error = std::string(UserCfgFilename) + ": " + m_user_cfg_unique_ins.get_last_error(); - return false; - } - /* 根据用户配置,覆盖原有的部分配置 */ - for (const auto& [name, path] : m_user_cfg_unique_ins.get_emulator_path()) { - m_general_cfg_unique_ins.set_emulator_path(name, path); - } - const auto& opt = m_general_cfg_unique_ins.get_options(); - - /* 加载模板图片资源 */ - // task所需要的模板资源 - m_templ_resource_unique_ins.append_load_required(Task.get_templ_required()); - // 基建所需要的模板资源 - m_templ_resource_unique_ins.append_load_required(m_infrast_cfg_unique_ins.get_templ_required()); - - if (!m_templ_resource_unique_ins.load(dir + TemplsFilename)) { - m_last_error = std::string(TemplsFilename) + ": " + m_templ_resource_unique_ins.get_last_error(); - return false; - } - - /* 加载OCR库所需要的资源 */ - //m_ocr_pack_unique_ins.set_param(opt.ocr_gpu_index, opt.ocr_thread_number); - if (!m_ocr_pack_unique_ins.load(dir + OcrResourceFilename)) { - m_last_error = std::string(OcrResourceFilename) + ": " + m_ocr_pack_unique_ins.get_last_error(); - return false; - } - /* 加载企鹅数据识别库所需要的资源 */ - m_penguin_pack_unique_ins.set_language(opt.penguin_report.server); - if (!m_penguin_pack_unique_ins.load(dir + PenguinResourceFilename)) { - m_last_error = std::string(PenguinResourceFilename) + ": " + m_penguin_pack_unique_ins.get_last_error(); - return false; - } - - return true; -} \ No newline at end of file +#include "Resource.h" + +#include +#include +#include + +#include + +#include "AsstDef.h" + +bool asst::Resource::load(const std::string& dir) +{ + constexpr static const char* TemplsFilename = "template"; + constexpr static const char* GeneralCfgFilename = "config.json"; + constexpr static const char* TaskDataFilename = "tasks.json"; + constexpr static const char* RecruitCfgFilename = "recruit.json"; + constexpr static const char* ItemCfgFilename = "item_index.json"; + constexpr static const char* InfrastCfgFilename = "infrast.json"; + constexpr static const char* UserCfgFilename = "..\\user.json"; + constexpr static const char* OcrResourceFilename = "PaddleOCR"; + constexpr static const char* PenguinResourceFilename = "penguin-stats-recognize"; + + /* 加载各个Json配置文件 */ + if (!m_general_cfg_unique_ins.load(dir + GeneralCfgFilename)) { + m_last_error = std::string(GeneralCfgFilename) + ": " + m_general_cfg_unique_ins.get_last_error(); + return false; + } + if (!Task.load(dir + TaskDataFilename)) { + m_last_error = std::string(TaskDataFilename) + ": " + Task.get_last_error(); + return false; + } + + if (!m_recruit_cfg_unique_ins.load(dir + RecruitCfgFilename)) { + m_last_error = std::string(RecruitCfgFilename) + ": " + m_recruit_cfg_unique_ins.get_last_error(); + return false; + } + if (!m_item_cfg_unique_ins.load(dir + ItemCfgFilename)) { + m_last_error = std::string(ItemCfgFilename) + ": " + m_item_cfg_unique_ins.get_last_error(); + return false; + } + if (!m_infrast_cfg_unique_ins.load(dir + InfrastCfgFilename)) { + m_last_error = std::string(InfrastCfgFilename) + ": " + m_infrast_cfg_unique_ins.get_last_error(); + return false; + } + + if (!m_user_cfg_unique_ins.load(dir + UserCfgFilename)) { + m_last_error = std::string(UserCfgFilename) + ": " + m_user_cfg_unique_ins.get_last_error(); + return false; + } + /* 根据用户配置,覆盖原有的部分配置 */ + for (const auto& [name, path] : m_user_cfg_unique_ins.get_emulator_path()) { + m_general_cfg_unique_ins.set_emulator_path(name, path); + } + const auto& opt = m_general_cfg_unique_ins.get_options(); + + /* 加载模板图片资源 */ + // task所需要的模板资源 + m_templ_resource_unique_ins.append_load_required(Task.get_templ_required()); + // 基建所需要的模板资源 + m_templ_resource_unique_ins.append_load_required(m_infrast_cfg_unique_ins.get_templ_required()); + + if (!m_templ_resource_unique_ins.load(dir + TemplsFilename)) { + m_last_error = std::string(TemplsFilename) + ": " + m_templ_resource_unique_ins.get_last_error(); + return false; + } + + /* 加载OCR库所需要的资源 */ + //m_ocr_pack_unique_ins.set_param(opt.ocr_gpu_index, opt.ocr_thread_number); + if (!m_ocr_pack_unique_ins.load(dir + OcrResourceFilename)) { + m_last_error = std::string(OcrResourceFilename) + ": " + m_ocr_pack_unique_ins.get_last_error(); + return false; + } + /* 加载企鹅数据识别库所需要的资源 */ + m_penguin_pack_unique_ins.set_language(opt.penguin_report.server); + if (!m_penguin_pack_unique_ins.load(dir + PenguinResourceFilename)) { + m_last_error = std::string(PenguinResourceFilename) + ": " + m_penguin_pack_unique_ins.get_last_error(); + return false; + } + + return true; +} diff --git a/src/MeoAssistant/Resource.h b/src/MeoAssistant/Resource.h index cd3836781a..5b1d3b7ca5 100644 --- a/src/MeoAssistant/Resource.h +++ b/src/MeoAssistant/Resource.h @@ -1,117 +1,117 @@ -#pragma once - -#include "AbstractResource.h" - -#include -#include - -#include "GeneralConfiger.h" -#include "InfrastConfiger.h" -#include "ItemConfiger.h" -#include "OcrPack.h" -#include "PenguinPack.h" -#include "RecruitConfiger.h" -#include "TaskData.h" -#include "TemplResource.h" -#include "UserConfiger.h" - -namespace asst -{ - class Resource : public AbstractResource - { - public: - virtual ~Resource() = default; - - static Resource& get_instance() - { - static Resource unique_instance; - return unique_instance; - } - - virtual bool load(const std::string& dir) override; - - TemplResource& templ() noexcept - { - return m_templ_resource_unique_ins; - } - GeneralConfiger& cfg() noexcept - { - return m_general_cfg_unique_ins; - } - RecruitConfiger& recruit() noexcept - { - return m_recruit_cfg_unique_ins; - } - ItemConfiger& item() noexcept - { - return m_item_cfg_unique_ins; - } - InfrastConfiger& infrast() noexcept - { - return m_infrast_cfg_unique_ins; - } - UserConfiger& user() noexcept - { - return m_user_cfg_unique_ins; - } - OcrPack& ocr() noexcept - { - return m_ocr_pack_unique_ins; - } - PenguinPack& penguin() noexcept - { - return m_penguin_pack_unique_ins; - } - - const TemplResource& templ() const noexcept - { - return m_templ_resource_unique_ins; - } - const GeneralConfiger& cfg() const noexcept - { - return m_general_cfg_unique_ins; - } - const RecruitConfiger& recruit() const noexcept - { - return m_recruit_cfg_unique_ins; - } - const ItemConfiger& item() const noexcept - { - return m_item_cfg_unique_ins; - } - const InfrastConfiger& infrast() const noexcept - { - return m_infrast_cfg_unique_ins; - } - const UserConfiger& user() const noexcept - { - return m_user_cfg_unique_ins; - } - const OcrPack& ocr() const noexcept - { - return m_ocr_pack_unique_ins; - } - const PenguinPack& penguin() const noexcept - { - return m_penguin_pack_unique_ins; - } - - Resource& operator=(const Resource&) = delete; - Resource& operator=(Resource&&) noexcept = delete; - - private: - Resource() = default; - - TemplResource m_templ_resource_unique_ins; - GeneralConfiger m_general_cfg_unique_ins; - RecruitConfiger m_recruit_cfg_unique_ins; - ItemConfiger m_item_cfg_unique_ins; - InfrastConfiger m_infrast_cfg_unique_ins; - UserConfiger m_user_cfg_unique_ins; - OcrPack m_ocr_pack_unique_ins; - PenguinPack m_penguin_pack_unique_ins; - }; - - //static auto& resource = Resource::get_instance(); -#define Resrc Resource::get_instance() -} +#pragma once + +#include "AbstractResource.h" + +#include +#include + +#include "GeneralConfiger.h" +#include "InfrastConfiger.h" +#include "ItemConfiger.h" +#include "OcrPack.h" +#include "PenguinPack.h" +#include "RecruitConfiger.h" +#include "TaskData.h" +#include "TemplResource.h" +#include "UserConfiger.h" + +namespace asst +{ + class Resource : public AbstractResource + { + public: + virtual ~Resource() = default; + + static Resource& get_instance() + { + static Resource unique_instance; + return unique_instance; + } + + virtual bool load(const std::string& dir) override; + + TemplResource& templ() noexcept + { + return m_templ_resource_unique_ins; + } + GeneralConfiger& cfg() noexcept + { + return m_general_cfg_unique_ins; + } + RecruitConfiger& recruit() noexcept + { + return m_recruit_cfg_unique_ins; + } + ItemConfiger& item() noexcept + { + return m_item_cfg_unique_ins; + } + InfrastConfiger& infrast() noexcept + { + return m_infrast_cfg_unique_ins; + } + UserConfiger& user() noexcept + { + return m_user_cfg_unique_ins; + } + OcrPack& ocr() noexcept + { + return m_ocr_pack_unique_ins; + } + PenguinPack& penguin() noexcept + { + return m_penguin_pack_unique_ins; + } + + const TemplResource& templ() const noexcept + { + return m_templ_resource_unique_ins; + } + const GeneralConfiger& cfg() const noexcept + { + return m_general_cfg_unique_ins; + } + const RecruitConfiger& recruit() const noexcept + { + return m_recruit_cfg_unique_ins; + } + const ItemConfiger& item() const noexcept + { + return m_item_cfg_unique_ins; + } + const InfrastConfiger& infrast() const noexcept + { + return m_infrast_cfg_unique_ins; + } + const UserConfiger& user() const noexcept + { + return m_user_cfg_unique_ins; + } + const OcrPack& ocr() const noexcept + { + return m_ocr_pack_unique_ins; + } + const PenguinPack& penguin() const noexcept + { + return m_penguin_pack_unique_ins; + } + + Resource& operator=(const Resource&) = delete; + Resource& operator=(Resource&&) noexcept = delete; + + private: + Resource() = default; + + TemplResource m_templ_resource_unique_ins; + GeneralConfiger m_general_cfg_unique_ins; + RecruitConfiger m_recruit_cfg_unique_ins; + ItemConfiger m_item_cfg_unique_ins; + InfrastConfiger m_infrast_cfg_unique_ins; + UserConfiger m_user_cfg_unique_ins; + OcrPack m_ocr_pack_unique_ins; + PenguinPack m_penguin_pack_unique_ins; + }; + + //static auto& resource = Resource::get_instance(); +#define Resrc Resource::get_instance() +} diff --git a/src/MeoAssistant/RuntimeStatus.cpp b/src/MeoAssistant/RuntimeStatus.cpp index 1ebc704409..1d02b77f39 100644 --- a/src/MeoAssistant/RuntimeStatus.cpp +++ b/src/MeoAssistant/RuntimeStatus.cpp @@ -1 +1 @@ -#include "RuntimeStatus.h" \ No newline at end of file +#include "RuntimeStatus.h" diff --git a/src/MeoAssistant/TaskData.cpp b/src/MeoAssistant/TaskData.cpp index bea0e55e00..8d4e8f76d7 100644 --- a/src/MeoAssistant/TaskData.cpp +++ b/src/MeoAssistant/TaskData.cpp @@ -1,203 +1,203 @@ -#include "TaskData.h" - -#include - -#include - -#include "AsstDef.h" -#include "GeneralConfiger.h" -#include "TemplResource.h" - -const std::shared_ptr asst::TaskData::get(const std::string& name) const noexcept -{ - if (auto iter = m_all_tasks_info.find(name); - iter != m_all_tasks_info.cend()) { - return iter->second; - } - else { - return nullptr; - } -} - -const std::unordered_set& asst::TaskData::get_templ_required() const noexcept -{ - return m_templ_required; -} - -std::shared_ptr asst::TaskData::get(std::string name) -{ - return m_all_tasks_info[std::move(name)]; -} - -void asst::TaskData::clear_cache() noexcept -{ - for (auto&& [name, ptr] : m_all_tasks_info) { - ptr->region_of_appeared = Rect(); - } -} - -bool asst::TaskData::parse(const json::value& json) -{ - for (const auto& [name, task_json] : json.as_object()) { - std::string algorithm_str = task_json.get("algorithm", "matchtemplate"); - std::transform(algorithm_str.begin(), algorithm_str.end(), algorithm_str.begin(), ::tolower); - AlgorithmType algorithm = AlgorithmType::Invaild; - if (algorithm_str == "matchtemplate") { - algorithm = AlgorithmType::MatchTemplate; - } - else if (algorithm_str == "justreturn") { - algorithm = AlgorithmType::JustReturn; - } - else if (algorithm_str == "ocrdetect") { - algorithm = AlgorithmType::OcrDetect; - } - // CompareHist是MatchTemplate的衍生算法,不应作为单独的配置参数出现 - // else if (algorithm_str == "comparehist") {} - else { - m_last_error = "algorithm error " + algorithm_str; - return false; - } - - std::shared_ptr task_info_ptr = nullptr; - switch (algorithm) { - case AlgorithmType::JustReturn: - task_info_ptr = std::make_shared(); - break; - case AlgorithmType::MatchTemplate: { - auto match_task_info_ptr = std::make_shared(); - match_task_info_ptr->templ_name = task_json.get("template", name + ".png"); - m_templ_required.emplace(match_task_info_ptr->templ_name); - - match_task_info_ptr->templ_threshold = task_json.get( - "templThreshold", TemplThresholdDefault); - match_task_info_ptr->special_threshold = task_json.get( - "specialThreshold", 0); - if (task_json.exist("maskRange")) { - match_task_info_ptr->mask_range = std::make_pair( - task_json.at("maskRange")[0].as_integer(), - task_json.at("maskRange")[1].as_integer()); - } - - task_info_ptr = match_task_info_ptr; - } break; - case AlgorithmType::OcrDetect: { - auto ocr_task_info_ptr = std::make_shared(); - for (const json::value& text : task_json.at("text").as_array()) { - ocr_task_info_ptr->text.emplace_back(text.as_string()); - } - ocr_task_info_ptr->need_full_match = task_json.get("need_match", false); - if (task_json.exist("ocrReplace")) { - for (const json::value& rep : task_json.at("ocrReplace").as_array()) { - ocr_task_info_ptr->replace_map.emplace(rep.as_array()[0].as_string(), rep.as_array()[1].as_string()); - } - } - task_info_ptr = ocr_task_info_ptr; - } break; - } - task_info_ptr->cache = task_json.get("cache", true); - task_info_ptr->algorithm = algorithm; - task_info_ptr->name = name; - std::string action = task_json.get("action", std::string()); - std::transform(action.begin(), action.end(), action.begin(), ::tolower); - if (action == "clickself") { - task_info_ptr->action = ProcessTaskAction::ClickSelf; - } - else if (action == "clickrand") { - task_info_ptr->action = ProcessTaskAction::ClickRand; - } - else if (action == "donothing" || action.empty()) { - task_info_ptr->action = ProcessTaskAction::DoNothing; - } - else if (action == "stop") { - task_info_ptr->action = ProcessTaskAction::Stop; - } - else if (action == "clickrect") { - task_info_ptr->action = ProcessTaskAction::ClickRect; - const json::value& rect_json = task_json.at("specificRect"); - task_info_ptr->specific_rect = Rect( - rect_json[0].as_integer(), - rect_json[1].as_integer(), - rect_json[2].as_integer(), - rect_json[3].as_integer()); - } - else if (action == "stagedrops") { - task_info_ptr->action = ProcessTaskAction::StageDrops; - } - else if (action == "swipetotheleft") { - task_info_ptr->action = ProcessTaskAction::SwipeToTheLeft; - } - else if (action == "swipetotheright") { - task_info_ptr->action = ProcessTaskAction::SwipeToTheRight; - } - else { - m_last_error = "Task: " + name + " error: " + action; - return false; - } - - task_info_ptr->max_times = task_json.get("maxTimes", INT_MAX); - if (task_json.exist("exceededNext")) { - const json::array& excceed_next_arr = task_json.at("exceededNext").as_array(); - for (const json::value& excceed_next : excceed_next_arr) { - task_info_ptr->exceeded_next.emplace_back(excceed_next.as_string()); - } - } - else { - task_info_ptr->exceeded_next.emplace_back("Stop"); - } - task_info_ptr->pre_delay = task_json.get("preDelay", 0); - task_info_ptr->rear_delay = task_json.get("rearDelay", 0); - if (task_json.exist("reduceOtherTimes")) { - const json::array& reduce_arr = task_json.at("reduceOtherTimes").as_array(); - for (const json::value& reduce : reduce_arr) { - task_info_ptr->reduce_other_times.emplace_back(reduce.as_string()); - } - } - if (task_json.exist("roi")) { - const json::array& area_arr = task_json.at("roi").as_array(); - int x = area_arr[0].as_integer(); - int y = area_arr[1].as_integer(); - int width = area_arr[2].as_integer(); - int height = area_arr[3].as_integer(); -#ifdef LOG_TRACE - if (x + width > WindowWidthDefault || y + height > WindowHeightDefault) { - m_last_error = name + " roi is out of bounds"; - return false; - } -#endif - task_info_ptr->roi = Rect(x, y, width, height); - } - else { - task_info_ptr->roi = Rect(); - } - - if (task_json.exist("next")) { - for (const json::value& next : task_json.at("next").as_array()) { - task_info_ptr->next.emplace_back(next.as_string()); - } - } - if (task_json.exist("rectMove")) { - const json::array& move_arr = task_json.at("rectMove").as_array(); - task_info_ptr->rect_move = Rect( - move_arr[0].as_integer(), - move_arr[1].as_integer(), - move_arr[2].as_integer(), - move_arr[3].as_integer()); - } - else { - task_info_ptr->rect_move = Rect(); - } - - m_all_tasks_info.emplace(name, task_info_ptr); - } -#ifdef LOG_TRACE - for (const auto& [name, task] : m_all_tasks_info) { - for (const auto& next : task->next) { - if (m_all_tasks_info.find(next) == m_all_tasks_info.cend()) { - m_last_error = name + "'s next " + next + " is null"; - return false; - } - } - } -#endif - return true; -} \ No newline at end of file +#include "TaskData.h" + +#include + +#include + +#include "AsstDef.h" +#include "GeneralConfiger.h" +#include "TemplResource.h" + +const std::shared_ptr asst::TaskData::get(const std::string& name) const noexcept +{ + if (auto iter = m_all_tasks_info.find(name); + iter != m_all_tasks_info.cend()) { + return iter->second; + } + else { + return nullptr; + } +} + +const std::unordered_set& asst::TaskData::get_templ_required() const noexcept +{ + return m_templ_required; +} + +std::shared_ptr asst::TaskData::get(std::string name) +{ + return m_all_tasks_info[std::move(name)]; +} + +void asst::TaskData::clear_cache() noexcept +{ + for (auto&& [name, ptr] : m_all_tasks_info) { + ptr->region_of_appeared = Rect(); + } +} + +bool asst::TaskData::parse(const json::value& json) +{ + for (const auto& [name, task_json] : json.as_object()) { + std::string algorithm_str = task_json.get("algorithm", "matchtemplate"); + std::transform(algorithm_str.begin(), algorithm_str.end(), algorithm_str.begin(), ::tolower); + AlgorithmType algorithm = AlgorithmType::Invaild; + if (algorithm_str == "matchtemplate") { + algorithm = AlgorithmType::MatchTemplate; + } + else if (algorithm_str == "justreturn") { + algorithm = AlgorithmType::JustReturn; + } + else if (algorithm_str == "ocrdetect") { + algorithm = AlgorithmType::OcrDetect; + } + // CompareHist是MatchTemplate的衍生算法,不应作为单独的配置参数出现 + // else if (algorithm_str == "comparehist") {} + else { + m_last_error = "algorithm error " + algorithm_str; + return false; + } + + std::shared_ptr task_info_ptr = nullptr; + switch (algorithm) { + case AlgorithmType::JustReturn: + task_info_ptr = std::make_shared(); + break; + case AlgorithmType::MatchTemplate: { + auto match_task_info_ptr = std::make_shared(); + match_task_info_ptr->templ_name = task_json.get("template", name + ".png"); + m_templ_required.emplace(match_task_info_ptr->templ_name); + + match_task_info_ptr->templ_threshold = task_json.get( + "templThreshold", TemplThresholdDefault); + match_task_info_ptr->special_threshold = task_json.get( + "specialThreshold", 0); + if (task_json.exist("maskRange")) { + match_task_info_ptr->mask_range = std::make_pair( + task_json.at("maskRange")[0].as_integer(), + task_json.at("maskRange")[1].as_integer()); + } + + task_info_ptr = match_task_info_ptr; + } break; + case AlgorithmType::OcrDetect: { + auto ocr_task_info_ptr = std::make_shared(); + for (const json::value& text : task_json.at("text").as_array()) { + ocr_task_info_ptr->text.emplace_back(text.as_string()); + } + ocr_task_info_ptr->need_full_match = task_json.get("need_match", false); + if (task_json.exist("ocrReplace")) { + for (const json::value& rep : task_json.at("ocrReplace").as_array()) { + ocr_task_info_ptr->replace_map.emplace(rep.as_array()[0].as_string(), rep.as_array()[1].as_string()); + } + } + task_info_ptr = ocr_task_info_ptr; + } break; + } + task_info_ptr->cache = task_json.get("cache", true); + task_info_ptr->algorithm = algorithm; + task_info_ptr->name = name; + std::string action = task_json.get("action", std::string()); + std::transform(action.begin(), action.end(), action.begin(), ::tolower); + if (action == "clickself") { + task_info_ptr->action = ProcessTaskAction::ClickSelf; + } + else if (action == "clickrand") { + task_info_ptr->action = ProcessTaskAction::ClickRand; + } + else if (action == "donothing" || action.empty()) { + task_info_ptr->action = ProcessTaskAction::DoNothing; + } + else if (action == "stop") { + task_info_ptr->action = ProcessTaskAction::Stop; + } + else if (action == "clickrect") { + task_info_ptr->action = ProcessTaskAction::ClickRect; + const json::value& rect_json = task_json.at("specificRect"); + task_info_ptr->specific_rect = Rect( + rect_json[0].as_integer(), + rect_json[1].as_integer(), + rect_json[2].as_integer(), + rect_json[3].as_integer()); + } + else if (action == "stagedrops") { + task_info_ptr->action = ProcessTaskAction::StageDrops; + } + else if (action == "swipetotheleft") { + task_info_ptr->action = ProcessTaskAction::SwipeToTheLeft; + } + else if (action == "swipetotheright") { + task_info_ptr->action = ProcessTaskAction::SwipeToTheRight; + } + else { + m_last_error = "Task: " + name + " error: " + action; + return false; + } + + task_info_ptr->max_times = task_json.get("maxTimes", INT_MAX); + if (task_json.exist("exceededNext")) { + const json::array& excceed_next_arr = task_json.at("exceededNext").as_array(); + for (const json::value& excceed_next : excceed_next_arr) { + task_info_ptr->exceeded_next.emplace_back(excceed_next.as_string()); + } + } + else { + task_info_ptr->exceeded_next.emplace_back("Stop"); + } + task_info_ptr->pre_delay = task_json.get("preDelay", 0); + task_info_ptr->rear_delay = task_json.get("rearDelay", 0); + if (task_json.exist("reduceOtherTimes")) { + const json::array& reduce_arr = task_json.at("reduceOtherTimes").as_array(); + for (const json::value& reduce : reduce_arr) { + task_info_ptr->reduce_other_times.emplace_back(reduce.as_string()); + } + } + if (task_json.exist("roi")) { + const json::array& area_arr = task_json.at("roi").as_array(); + int x = area_arr[0].as_integer(); + int y = area_arr[1].as_integer(); + int width = area_arr[2].as_integer(); + int height = area_arr[3].as_integer(); +#ifdef LOG_TRACE + if (x + width > WindowWidthDefault || y + height > WindowHeightDefault) { + m_last_error = name + " roi is out of bounds"; + return false; + } +#endif + task_info_ptr->roi = Rect(x, y, width, height); + } + else { + task_info_ptr->roi = Rect(); + } + + if (task_json.exist("next")) { + for (const json::value& next : task_json.at("next").as_array()) { + task_info_ptr->next.emplace_back(next.as_string()); + } + } + if (task_json.exist("rectMove")) { + const json::array& move_arr = task_json.at("rectMove").as_array(); + task_info_ptr->rect_move = Rect( + move_arr[0].as_integer(), + move_arr[1].as_integer(), + move_arr[2].as_integer(), + move_arr[3].as_integer()); + } + else { + task_info_ptr->rect_move = Rect(); + } + + m_all_tasks_info.emplace(name, task_info_ptr); + } +#ifdef LOG_TRACE + for (const auto& [name, task] : m_all_tasks_info) { + for (const auto& next : task->next) { + if (m_all_tasks_info.find(next) == m_all_tasks_info.cend()) { + m_last_error = name + "'s next " + next + " is null"; + return false; + } + } + } +#endif + return true; +} diff --git a/src/MeoAssistant/TemplResource.cpp b/src/MeoAssistant/TemplResource.cpp index 99b43de2eb..4566327ada 100644 --- a/src/MeoAssistant/TemplResource.cpp +++ b/src/MeoAssistant/TemplResource.cpp @@ -1,51 +1,51 @@ -#include "TemplResource.h" - -#include -#include -#include - -void asst::TemplResource::append_load_required(std::unordered_set required) noexcept -{ - m_templs_filename.insert( - std::make_move_iterator(required.begin()), - std::make_move_iterator(required.end())); -} - -bool asst::TemplResource::load(const std::string& dir) -{ - for (const std::string& filename : m_templs_filename) { - std::string filepath = dir + "/" + filename; - if (std::filesystem::exists(filepath)) { - cv::Mat templ = cv::imread(filepath); - emplace_templ(filename, std::move(templ)); - } - else { - m_last_error = filepath + " not exists"; - return false; - } - } - - return true; -} - -bool asst::TemplResource::exist_templ(const std::string& key) const noexcept -{ - return m_templs.find(key) != m_templs.cend(); -} - -const cv::Mat asst::TemplResource::get_templ(const std::string& key) const noexcept -{ - if (auto iter = m_templs.find(key); - iter != m_templs.cend()) { - return iter->second; - } - else { - const static cv::Mat empty; - return empty; - } -} - -void asst::TemplResource::emplace_templ(std::string key, cv::Mat templ) -{ - m_templs.emplace(std::move(key), std::move(templ)); -} +#include "TemplResource.h" + +#include +#include +#include + +void asst::TemplResource::append_load_required(std::unordered_set required) noexcept +{ + m_templs_filename.insert( + std::make_move_iterator(required.begin()), + std::make_move_iterator(required.end())); +} + +bool asst::TemplResource::load(const std::string& dir) +{ + for (const std::string& filename : m_templs_filename) { + std::string filepath = dir + "/" + filename; + if (std::filesystem::exists(filepath)) { + cv::Mat templ = cv::imread(filepath); + emplace_templ(filename, std::move(templ)); + } + else { + m_last_error = filepath + " not exists"; + return false; + } + } + + return true; +} + +bool asst::TemplResource::exist_templ(const std::string& key) const noexcept +{ + return m_templs.find(key) != m_templs.cend(); +} + +const cv::Mat asst::TemplResource::get_templ(const std::string& key) const noexcept +{ + if (auto iter = m_templs.find(key); + iter != m_templs.cend()) { + return iter->second; + } + else { + const static cv::Mat empty; + return empty; + } +} + +void asst::TemplResource::emplace_templ(std::string key, cv::Mat templ) +{ + m_templs.emplace(std::move(key), std::move(templ)); +} diff --git a/src/MeoAssistant/UserConfiger.cpp b/src/MeoAssistant/UserConfiger.cpp index a265e009cf..3de507f872 100644 --- a/src/MeoAssistant/UserConfiger.cpp +++ b/src/MeoAssistant/UserConfiger.cpp @@ -45,4 +45,4 @@ bool asst::UserConfiger::save() ofs.close(); return true; -} \ No newline at end of file +} diff --git a/src/MeoAsstGui/App.xaml b/src/MeoAsstGui/App.xaml index 3f9a9f1978..fde345edd3 100644 --- a/src/MeoAsstGui/App.xaml +++ b/src/MeoAsstGui/App.xaml @@ -2,28 +2,28 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MeoAsstGui" - xmlns:s="https://github.com/canton7/Stylet"> - - - - - - - - - - - - - - - - + xmlns:s="https://github.com/canton7/Stylet"> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MeoAsstGui/Properties/AssemblyInfo.cs b/src/MeoAsstGui/Properties/AssemblyInfo.cs index 79222c16e0..749a476efb 100644 --- a/src/MeoAsstGui/Properties/AssemblyInfo.cs +++ b/src/MeoAsstGui/Properties/AssemblyInfo.cs @@ -1,14 +1,14 @@ -// MeoAsstGui - A part of the MeoAssistantArknights project -// Copyright (C) 2021 MistEO and Contributors -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY - +// MeoAsstGui - A part of the MeoAssistantArknights project +// Copyright (C) 2021 MistEO and Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY + using System.Reflection; using System.Resources; using System.Runtime.InteropServices; @@ -41,12 +41,12 @@ using System.Windows; //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] [assembly: ThemeInfo( - ResourceDictionaryLocation.None, //主题特定资源词典所处位置 - //(未在页面中找到资源时使用, - //或应用程序资源字典中找到时使用) - ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 - //(未在页面中找到资源时使用, - //、应用程序或任何主题专用资源字典中找到时使用) + ResourceDictionaryLocation.None, //主题特定资源词典所处位置 + //(未在页面中找到资源时使用, + //或应用程序资源字典中找到时使用) + ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 + //(未在页面中找到资源时使用, + //、应用程序或任何主题专用资源字典中找到时使用) )] // 程序集的版本信息由下列四个值组成: @@ -61,4 +61,4 @@ using System.Windows; // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: NeutralResourcesLanguage("zh")] \ No newline at end of file +[assembly: NeutralResourcesLanguage("zh")] diff --git a/src/MeoAsstGui/Properties/Resources.Designer.cs b/src/MeoAsstGui/Properties/Resources.Designer.cs index b4f9f392e9..18701c716e 100644 --- a/src/MeoAsstGui/Properties/Resources.Designer.cs +++ b/src/MeoAsstGui/Properties/Resources.Designer.cs @@ -8,10 +8,11 @@ // //------------------------------------------------------------------------------ -namespace MeoAsstGui.Properties { +namespace MeoAsstGui.Properties +{ using System; - - + + /// /// 一个强类型的资源类,用于查找本地化的字符串等。 /// @@ -22,40 +23,48 @@ namespace MeoAsstGui.Properties { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - + internal class Resources + { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { + internal Resources() + { } - + /// /// 返回此类使用的缓存的 ResourceManager 实例。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MeoAsstGui.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// 重写当前线程的 CurrentUICulture 属性,对 /// 使用此强类型资源类的所有资源查找执行重写。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { + internal static global::System.Globalization.CultureInfo Culture + { + get + { return resourceCulture; } - set { + set + { resourceCulture = value; } } diff --git a/src/MeoAsstGui/Properties/Settings.Designer.cs b/src/MeoAsstGui/Properties/Settings.Designer.cs index 46526ce88e..af956e4392 100644 --- a/src/MeoAsstGui/Properties/Settings.Designer.cs +++ b/src/MeoAsstGui/Properties/Settings.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // 此代码由工具生成。 // 运行时版本:4.0.30319.42000 @@ -8,17 +8,21 @@ // //------------------------------------------------------------------------------ -namespace MeoAsstGui.Properties { - - +namespace MeoAsstGui.Properties +{ + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { + + public static Settings Default + { + get + { return defaultInstance; } } diff --git a/src/MeoAsstGui/Resources/Icon.xaml b/src/MeoAsstGui/Resources/Icon.xaml index 8d1fea344f..96c9215a43 100644 --- a/src/MeoAsstGui/Resources/Icon.xaml +++ b/src/MeoAsstGui/Resources/Icon.xaml @@ -1,4 +1,4 @@ - diff --git a/src/MeoAsstGui/Resources/Styles.xaml b/src/MeoAsstGui/Resources/Styles.xaml index cee5a6099a..ba315817ec 100644 --- a/src/MeoAsstGui/Resources/Styles.xaml +++ b/src/MeoAsstGui/Resources/Styles.xaml @@ -1,5 +1,5 @@ -