diff --git a/CMakeLists.txt b/CMakeLists.txt index cba69e67fb..707f7284cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,13 +7,16 @@ include_directories(include 3rdparty/include) aux_source_directory(src/MeoAssistant SRC) add_definitions(-DASST_DLL_EXPORTS) -add_compile_options("$<$:/utf-8>") -add_compile_options("$<$:/utf-8>") -add_compile_options("$<$:/MP>") -add_compile_options("$<$:/MP>") +if (WIN32) + add_compile_options("$<$:/utf-8>") + add_compile_options("$<$:/utf-8>") + add_compile_options("$<$:/MP>") + add_compile_options("$<$:/MP>") + + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") +endif() -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") set(CMAKE_CXX_STANDARD 17) add_library(MeoAssistant SHARED ${SRC}) @@ -27,7 +30,7 @@ if (WIN32) else () find_package(OpenCV REQUIRED) endif () - +include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(MeoAssistant ${OpenCV} ${PaddleOCR_LIB} ${Penguin_LIB} ${MeoJson_LIB}) diff --git a/src/MeoAssistant/AbstractImageAnalyzer.cpp b/src/MeoAssistant/AbstractImageAnalyzer.cpp index aa15d11e28..475b828345 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.cpp +++ b/src/MeoAssistant/AbstractImageAnalyzer.cpp @@ -1,97 +1,97 @@ -#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 d912ee6ace..3437c7e64b 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.h +++ b/src/MeoAssistant/AbstractImageAnalyzer.h @@ -10,14 +10,14 @@ namespace asst { public: AbstractImageAnalyzer() = default; - AbstractImageAnalyzer(const cv::Mat& image); - AbstractImageAnalyzer(const cv::Mat& image, const Rect& roi); + 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_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; @@ -29,7 +29,7 @@ namespace asst AbstractImageAnalyzer& operator=(AbstractImageAnalyzer&&) = delete; protected: - static Rect empty_rect_to_full(const Rect& rect, const cv::Mat& image) noexcept; + static Rect empty_rect_to_full(const Rect& rect, const cv::Mat image) noexcept; cv::Mat m_image; Rect m_roi; diff --git a/src/MeoAssistant/AbstractTask.cpp b/src/MeoAssistant/AbstractTask.cpp index ea740bbe57..a78de1338a 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::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::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/AbstractTask.h b/src/MeoAssistant/AbstractTask.h index 0efd851eca..25dff967cf 100644 --- a/src/MeoAssistant/AbstractTask.h +++ b/src/MeoAssistant/AbstractTask.h @@ -32,7 +32,7 @@ namespace asst virtual bool on_run_fails() { return true; } bool sleep(unsigned millisecond); - bool save_image(const cv::Mat& image, const std::string& dir); + bool save_image(const cv::Mat image, const std::string& dir); bool need_exit() const; virtual void click_return_button(); diff --git a/src/MeoAssistant/AipOcr.cpp b/src/MeoAssistant/AipOcr.cpp index 06912fba2c..5dfe771aed 100644 --- a/src/MeoAssistant/AipOcr.cpp +++ b/src/MeoAssistant/AipOcr.cpp @@ -47,7 +47,7 @@ bool asst::AipOcr::request_access_token(const std::string& client_id, const std: return true; } -bool asst::AipOcr::request_ocr_general(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) +bool asst::AipOcr::request_ocr_general(const cv::Mat image, std::vector& out_result, const TextRectProc& pred) { LogTraceFunction; @@ -62,7 +62,7 @@ bool asst::AipOcr::request_ocr_general(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) +bool asst::AipOcr::request_ocr_accurate(const cv::Mat image, std::vector& out_result, const TextRectProc& pred) { LogTraceFunction; @@ -77,7 +77,7 @@ bool asst::AipOcr::request_ocr_accurate(const cv::Mat& image, std::vector& out_result, const TextRectProc& 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; diff --git a/src/MeoAssistant/AipOcr.h b/src/MeoAssistant/AipOcr.h index 284a9d7c28..f22fbda1d1 100644 --- a/src/MeoAssistant/AipOcr.h +++ b/src/MeoAssistant/AipOcr.h @@ -27,15 +27,15 @@ namespace asst } bool request_access_token(const std::string& client_id, const std::string& client_secret); - bool request_ocr_general(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred = nullptr); - bool request_ocr_accurate(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred = nullptr); + bool request_ocr_general(const cv::Mat image, std::vector& out_result, const TextRectProc& pred = nullptr); + bool request_ocr_accurate(const cv::Mat image, std::vector& out_result, const TextRectProc& pred = nullptr); private: AipOcr() = default; AipOcr(const AipOcr&) = default; AipOcr(AipOcr&&) noexcept = default; - bool request_ocr_and_parse(std::string_view cmd_fmt, const cv::Mat& image, std::vector& out_result, const TextRectProc& pred = nullptr); + bool request_ocr_and_parse(std::string_view cmd_fmt, const cv::Mat image, std::vector& out_result, const TextRectProc& pred = nullptr); bool parse_response(const json::value& json, std::vector& out_result, const TextRectProc& pred = nullptr); std::string m_access_token; diff --git a/src/MeoAssistant/Assistant.cpp b/src/MeoAssistant/Assistant.cpp index c4395bc605..a1d480b1ce 100644 --- a/src/MeoAssistant/Assistant.cpp +++ b/src/MeoAssistant/Assistant.cpp @@ -1,641 +1,641 @@ -#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); - - // 进入对应的关卡 - auto stage_task_ptr = std::make_shared(task_callback, (void*)this); - stage_task_ptr->set_task_chain(TaskChain); - stage_task_ptr->set_tasks({ stage }); - - // 开始战斗任务 - 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); - - 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, 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; - } - - auto task_ptr = std::make_shared(task_callback, (void*)this); - task_ptr->set_task_chain(task_chain); - task_ptr->set_tasks({ task }); - 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() -{ - 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); + + // 进入对应的关卡 + auto stage_task_ptr = std::make_shared(task_callback, (void*)this); + stage_task_ptr->set_task_chain(TaskChain); + stage_task_ptr->set_tasks({ stage }); + + // 开始战斗任务 + 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); + + 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, 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; + } + + auto task_ptr = std::make_shared(task_callback, (void*)this); + task_ptr->set_task_chain(task_chain); + task_ptr->set_tasks({ task }); + 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() +{ + 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/AsstDef.h b/src/MeoAssistant/AsstDef.h index cfcbe29a9c..7250157629 100644 --- a/src/MeoAssistant/AsstDef.h +++ b/src/MeoAssistant/AsstDef.h @@ -191,7 +191,6 @@ namespace asst ProcessTaskAction action = // 要进行的操作 ProcessTaskAction::Invalid; std::vector next; // 下一个可能的任务(列表) - [[ deprecated ]] int exec_times = 0; // [已废弃] 任务已执行了多少次 int max_times = INT_MAX; // 任务最多执行多少次 std::vector exceeded_next; // 达到最多次数了之后,下一个可能的任务(列表) std::vector reduce_other_times; // 执行了该任务后,需要减少别的任务的执行次数。例如执行了吃理智药,则说明上一次点击蓝色开始行动按钮没生效,所以蓝色开始行动要-1 diff --git a/src/MeoAssistant/AsstUtils.hpp b/src/MeoAssistant/AsstUtils.hpp index 4f750f119e..e372800387 100644 --- a/src/MeoAssistant/AsstUtils.hpp +++ b/src/MeoAssistant/AsstUtils.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #endif namespace asst @@ -69,7 +70,7 @@ namespace asst 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.%03d", buff, tv.tv_usec / 1000); + sprintf(buff, "%s.%03ld", buff, tv.tv_usec / 1000); #endif // END _WIN32 return buff; } @@ -218,55 +219,56 @@ namespace asst #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 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); + 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 m_child = fork(); - if (m_child == 0) { + int child = fork(); + if (child == 0) { // child process - dup2(m_pipe_in[PIPE_READ], STDIN_FILENO); - dup2(m_pipe_out[PIPE_WRITE], STDOUT_FILENO); - dup2(m_pipe_out[PIPE_WRITE], STDERR_FILENO); + 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(m_pipe_in[PIPE_READ]); - close(m_pipe_in[PIPE_WRITE]); - close(m_pipe_out[PIPE_READ]); - close(m_pipe_out[PIPE_WRITE]); + 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 (m_child > 0) { + else if (child > 0) { // parent process - // LogTraceScope("Parent process: " + cmd); - std::unique_ptr pipe_buffer = std::make_unique(BuffSize); // close unused file descriptors, these are for child only - close(m_pipe_in[PIPE_READ]); - close(m_pipe_out[PIPE_WRITE]); + close(pipe_in[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); + + std::unique_ptr pipe_buffer(new char[BuffSize + 1]); do { - ssize_t read_num = read(m_pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); + 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(m_pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); + read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), BuffSize); }; - } while (::waitpid(m_child, NULL, WNOHANG) == 0); + } while (::waitpid(child, NULL, WNOHANG) == 0); - close(m_pipe_in[PIPE_WRITE]); - close(m_pipe_out[PIPE_READ]); + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); } else { // failed to create child process - close(m_pipe_in[PIPE_READ]); - close(m_pipe_in[PIPE_WRITE]); - close(m_pipe_out[PIPE_READ]); - close(m_pipe_out[PIPE_WRITE]); + close(pipe_in[PIPE_READ]); + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); } #endif return pipe_str; diff --git a/src/MeoAssistant/AutoRecruitTask.cpp b/src/MeoAssistant/AutoRecruitTask.cpp index 138befec15..1c8fb2cbf6 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; -} +#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 051ea28676..4da91b02d1 100644 --- a/src/MeoAssistant/Controller.cpp +++ b/src/MeoAssistant/Controller.cpp @@ -330,7 +330,7 @@ bool asst::Controller::try_capture(const EmulatorInfo & info, bool without_handl Log.error("Capture handle is only supported on Windows!"); return false; #endif -} + } else { // 使用辅助自带的标准adb m_emulator_info = info; #ifdef _WIN32 @@ -452,8 +452,9 @@ std::pair> asst::Controller::call_command(const // parent process // LogTraceScope("Parent process: " + cmd); constexpr int BuffSize = 4096; - std::unique_ptr pipe_buffer = std::make_unique(BuffSize); + 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) { @@ -571,6 +572,7 @@ bool asst::Controller::screencap() { LogTraceFunction; +#if 0 auto&& [ret, data] = call_command(m_emulator_info.adb.screencap); if (ret && !data.empty()) { if (m_image_convert_lf) { @@ -595,6 +597,10 @@ bool asst::Controller::screencap() 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); @@ -711,7 +717,6 @@ int asst::Controller::swipe_without_scale(const Rect & r1, const Rect & r2, int cv::Mat asst::Controller::get_image(bool raw) { - LogTraceFunction; // 有些模拟器adb偶尔会莫名其妙截图失败,多试几次 for (int i = 0; i != 20; ++i) { if (screencap()) { @@ -726,9 +731,9 @@ cv::Mat asst::Controller::get_image(bool raw) if (m_cache_image.empty()) { return m_cache_image; } - const static cv::Size newsize(m_scale_size.first, m_scale_size.second); - cv::Mat resize_mat; - cv::resize(m_cache_image, resize_mat, newsize, cv::INPAINT_NS); - return resize_mat; + 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/CreditShopImageAnalyzer.h b/src/MeoAssistant/CreditShopImageAnalyzer.h index bc1c463817..11727747e5 100644 --- a/src/MeoAssistant/CreditShopImageAnalyzer.h +++ b/src/MeoAssistant/CreditShopImageAnalyzer.h @@ -7,7 +7,7 @@ namespace asst { public: using AbstractImageAnalyzer::AbstractImageAnalyzer; - CreditShopImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; + CreditShopImageAnalyzer(const cv::Mat image, const Rect& roi) = delete; virtual ~CreditShopImageAnalyzer() = default; virtual bool analyze() override; @@ -23,7 +23,7 @@ namespace asst { AbstractImageAnalyzer::set_roi(roi); } - virtual void set_image(const cv::Mat& image, const Rect& roi) + virtual void set_image(const cv::Mat image, const Rect& roi) { AbstractImageAnalyzer::set_image(image, roi); } diff --git a/src/MeoAssistant/CreditShoppingTask.cpp b/src/MeoAssistant/CreditShoppingTask.cpp index aeb9a90b7e..257cd57724 100644 --- a/src/MeoAssistant/CreditShoppingTask.cpp +++ b/src/MeoAssistant/CreditShoppingTask.cpp @@ -19,7 +19,7 @@ bool asst::CreditShoppingTask::_run() }; m_callback(AsstMsg::TaskStart, task_start_json, m_callback_arg); - const cv::Mat& image = Ctrler.get_image(); + const cv::Mat image = Ctrler.get_image(); CreditShopImageAnalyzer shop_analyzer(image); if (!shop_analyzer.analyze()) { @@ -44,7 +44,7 @@ bool asst::CreditShoppingTask::_run() const auto buy_it_task_ptr = std::dynamic_pointer_cast( task.get("CreditShop-BuyIt")); - const cv::Mat& buy_image = Ctrler.get_image(); + const cv::Mat buy_image = Ctrler.get_image(); MatchImageAnalyzer buy_it_analyzer(buy_image); buy_it_analyzer.set_task_info(*buy_it_task_ptr); if (!buy_it_analyzer.analyze()) { @@ -63,7 +63,7 @@ bool asst::CreditShoppingTask::_run() sleep(rare_delay); // 识别是否信用不足无法购买 - const cv::Mat& prompt_image = Ctrler.get_image(); + const cv::Mat prompt_image = Ctrler.get_image(); const auto no_money_task_ptr = std::dynamic_pointer_cast( task.get("CreditShop-NoMoney")); diff --git a/src/MeoAssistant/InfrastAbstractTask.cpp b/src/MeoAssistant/InfrastAbstractTask.cpp index ca59ce86cb..b6ef236ac5 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); -} +#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/InfrastClueVacancyImageAnalyzer.h b/src/MeoAssistant/InfrastClueVacancyImageAnalyzer.h index eb17d6bcb2..a79949b09d 100644 --- a/src/MeoAssistant/InfrastClueVacancyImageAnalyzer.h +++ b/src/MeoAssistant/InfrastClueVacancyImageAnalyzer.h @@ -8,7 +8,7 @@ namespace asst public: using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~InfrastClueVacancyImageAnalyzer() = default; - InfrastClueVacancyImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; + InfrastClueVacancyImageAnalyzer(const cv::Mat image, const Rect& roi) = delete; virtual bool analyze() override; constexpr static int MaxNumOfClue = 7; @@ -29,7 +29,7 @@ namespace asst { AbstractImageAnalyzer::set_roi(roi); } - virtual void set_image(const cv::Mat& image, const Rect& roi) + virtual void set_image(const cv::Mat image, const Rect& roi) { AbstractImageAnalyzer::set_image(image, roi); } diff --git a/src/MeoAssistant/InfrastDormTask.cpp b/src/MeoAssistant/InfrastDormTask.cpp index 51a16d9b4e..3c95e0cccc 100644 --- a/src/MeoAssistant/InfrastDormTask.cpp +++ b/src/MeoAssistant/InfrastDormTask.cpp @@ -1,149 +1,149 @@ -#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); - - bool random_select = false; - 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(); - if (!random_select) { - swipe_to_the_left_of_operlist(); - } - else { - swipe_of_operlist(); - } - - 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; - } - if (!random_select) { - switch (oper.smiley.type) { - case infrast::SmileyType::Rest: - // 如果当前页面休息完成的人数超过5个,说明已经已经把所有心情不满的滑过一遍、没有更多的了 - // 从这时候开始,随机选人,尽量把宿舍填满(游戏机制,可以加信赖,还有些别的加成什么的) - if (++num_of_resting > MaxNumOfOpers) { - Log.trace("num_of_resting:", num_of_resting, ", enable random"); - random_select = true; - continue; - } - 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; - } - } - else { - 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; - } - } - } - } - if (num_of_selected >= MaxNumOfOpers) { - Log.trace("num_of_selected:", num_of_selected, ", just break"); - break; - } - swipe_of_operlist(); - } - click_confirm_button(); - click_return_button(); - } - 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; -//} +#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); + + bool random_select = false; + 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(); + if (!random_select) { + swipe_to_the_left_of_operlist(); + } + else { + swipe_of_operlist(); + } + + 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; + } + if (!random_select) { + switch (oper.smiley.type) { + case infrast::SmileyType::Rest: + // 如果当前页面休息完成的人数超过5个,说明已经已经把所有心情不满的滑过一遍、没有更多的了 + // 从这时候开始,随机选人,尽量把宿舍填满(游戏机制,可以加信赖,还有些别的加成什么的) + if (++num_of_resting > MaxNumOfOpers) { + Log.trace("num_of_resting:", num_of_resting, ", enable random"); + random_select = true; + continue; + } + 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; + } + } + else { + 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; + } + } + } + } + if (num_of_selected >= MaxNumOfOpers) { + Log.trace("num_of_selected:", num_of_selected, ", just break"); + break; + } + swipe_of_operlist(); + } + click_confirm_button(); + click_return_button(); + } + 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/InfrastFacilityImageAnalyzer.h b/src/MeoAssistant/InfrastFacilityImageAnalyzer.h index 543f26a6b7..2c2159324d 100644 --- a/src/MeoAssistant/InfrastFacilityImageAnalyzer.h +++ b/src/MeoAssistant/InfrastFacilityImageAnalyzer.h @@ -8,7 +8,7 @@ namespace asst public: using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~InfrastFacilityImageAnalyzer() = default; - InfrastFacilityImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; + InfrastFacilityImageAnalyzer(const cv::Mat image, const Rect& roi) = delete; virtual bool analyze() override; @@ -53,7 +53,7 @@ namespace asst { AbstractImageAnalyzer::set_roi(roi); } - virtual void set_image(const cv::Mat& image, const Rect& roi) + virtual void set_image(const cv::Mat image, const Rect& roi) { AbstractImageAnalyzer::set_image(image, roi); } diff --git a/src/MeoAssistant/InfrastInfoTask.cpp b/src/MeoAssistant/InfrastInfoTask.cpp index 34b3735732..110863fa59 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; -} +#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/InfrastOperImageAnalyzer.cpp b/src/MeoAssistant/InfrastOperImageAnalyzer.cpp index 1b35ecf19c..74a8009e56 100644 --- a/src/MeoAssistant/InfrastOperImageAnalyzer.cpp +++ b/src/MeoAssistant/InfrastOperImageAnalyzer.cpp @@ -445,7 +445,7 @@ void asst::InfrastOperImageAnalyzer::doing_analyze() } } -std::string asst::InfrastOperImageAnalyzer::hash_calc(const cv::Mat& image) +std::string asst::InfrastOperImageAnalyzer::hash_calc(const cv::Mat image) { //constexpr static int HashKernelSize = 16; const static cv::Size HashKernel(16, 16); diff --git a/src/MeoAssistant/InfrastOperImageAnalyzer.h b/src/MeoAssistant/InfrastOperImageAnalyzer.h index e5fb5ea758..0a0c5f1261 100644 --- a/src/MeoAssistant/InfrastOperImageAnalyzer.h +++ b/src/MeoAssistant/InfrastOperImageAnalyzer.h @@ -23,7 +23,7 @@ namespace asst using AbstractImageAnalyzer::AbstractImageAnalyzer; virtual ~InfrastOperImageAnalyzer() = default; - InfrastOperImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; + InfrastOperImageAnalyzer(const cv::Mat image, const Rect& roi) = delete; virtual bool analyze() override; @@ -55,7 +55,7 @@ namespace asst { AbstractImageAnalyzer::set_roi(roi); } - virtual void set_image(const cv::Mat& image, const Rect& roi) + virtual void set_image(const cv::Mat image, const Rect& roi) { AbstractImageAnalyzer::set_image(image, roi); } @@ -68,7 +68,7 @@ namespace asst void selected_analyze(); void doing_analyze(); - static std::string hash_calc(const cv::Mat& image); + static std::string hash_calc(const cv::Mat image); std::string m_facility; std::vector m_result; diff --git a/src/MeoAssistant/InfrastProductionTask.cpp b/src/MeoAssistant/InfrastProductionTask.cpp index fa18219224..2a87f9d0fa 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); - - // 使用无人机 - if (cur_product == m_uses_of_drones) { - if (use_drone()) { - m_uses_of_drones = "_Used"; - } - } - - 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(); - swipe_to_the_left_of_operlist(2); - - 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(); - continue; - } - break; - } - click_confirm_button(); - } - 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(*this, { task_name }); - return task.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); + + // 使用无人机 + if (cur_product == m_uses_of_drones) { + if (use_drone()) { + m_uses_of_drones = "_Used"; + } + } + + 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(); + swipe_to_the_left_of_operlist(2); + + 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(); + continue; + } + break; + } + click_confirm_button(); + } + 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(*this, { task_name }); + return task.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 508c89d29b..ac0b8b5141 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(*this, { "InfrastClueNew" }); - task.set_retry_times(5); - return task.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(); - swipe_to_the_left_of_operlist(); - - 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(); - 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; -} +#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(*this, { "InfrastClueNew" }); + task.set_retry_times(5); + return task.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(); + swipe_to_the_left_of_operlist(); + + 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(); + 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/Linux编译教程.md b/src/MeoAssistant/Linux编译教程.md index 5e0c86d217..1dc973dbf4 100644 --- a/src/MeoAssistant/Linux编译教程.md +++ b/src/MeoAssistant/Linux编译教程.md @@ -17,11 +17,12 @@ 1. 使用我魔改了接口的版本:https://github.com/MistEO/PaddleOCR 2. 参考 [这个教程](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.3/deploy/cpp_infer#readme) 3. PaddlePaddle 直接[下载](https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0/guides/05_inference_deployment/inference/build_and_install_lib_cn.html)即可 +4. 如果 cmake 找不到 opencv,可以尝试修改 CMakeLists.txt 里 49 行,非 Win32 下的 Opencv 查找路径 编译选项参考 ```bash -cmake ../ -DPADDLE_LIB=/your_path/paddle_inference/ -D OPENCV_DIR=/your_path_to_opencv/ -DBUILD_SHARED=ON -DWITH_MKL=OFF -DWITH_STATIC_LIB=OFF +cmake ../ -DPADDLE_LIB=/your_path/paddle_inference/ -D OPENCV_DIR=/your_path_to_opencv/ -DWITH_STATIC_LIB=OFF -DBUILD_SHARED=ON # 若设备不支持 MKL, 可再额外添加 -DWITH_MKL=OFF 选项,并下载对应的 PaddlePaddle 预测库版本 ``` ### meojson diff --git a/src/MeoAssistant/MatchImageAnalyzer.cpp b/src/MeoAssistant/MatchImageAnalyzer.cpp index 6cc18006c2..44c2f505d1 100644 --- a/src/MeoAssistant/MatchImageAnalyzer.cpp +++ b/src/MeoAssistant/MatchImageAnalyzer.cpp @@ -4,7 +4,7 @@ #include "Logger.hpp" #include "Resource.h" -asst::MatchImageAnalyzer::MatchImageAnalyzer(const cv::Mat& image, const Rect& roi, std::string templ_name, double templ_thres, double hist_thres) +asst::MatchImageAnalyzer::MatchImageAnalyzer(const cv::Mat image, const Rect& roi, std::string templ_name, double templ_thres, double hist_thres) : AbstractImageAnalyzer(image, roi), m_templ_name(std::move(templ_name)), m_templ_thres(templ_thres) @@ -14,7 +14,7 @@ asst::MatchImageAnalyzer::MatchImageAnalyzer(const cv::Mat& image, const Rect& r bool asst::MatchImageAnalyzer::analyze() { - const cv::Mat& templ = Resrc.templ().get_templ(m_templ_name); + const cv::Mat templ = Resrc.templ().get_templ(m_templ_name); if (templ.empty()) { Log.error("templ is empty!"); return false; @@ -22,7 +22,7 @@ bool asst::MatchImageAnalyzer::analyze() return match_templ(templ); } -bool asst::MatchImageAnalyzer::match_templ(const cv::Mat& templ) +bool asst::MatchImageAnalyzer::match_templ(const cv::Mat templ) { cv::Mat matched; diff --git a/src/MeoAssistant/MatchImageAnalyzer.h b/src/MeoAssistant/MatchImageAnalyzer.h index d4b02009f2..c6589997c0 100644 --- a/src/MeoAssistant/MatchImageAnalyzer.h +++ b/src/MeoAssistant/MatchImageAnalyzer.h @@ -7,7 +7,7 @@ namespace asst { public: using AbstractImageAnalyzer::AbstractImageAnalyzer; - MatchImageAnalyzer(const cv::Mat& image, const Rect& roi, std::string templ_name, double templ_thres = 0.0, double hist_thres = 0.0); + MatchImageAnalyzer(const cv::Mat image, const Rect& roi, std::string templ_name, double templ_thres = 0.0, double hist_thres = 0.0); virtual ~MatchImageAnalyzer() = default; virtual bool analyze() override; @@ -55,7 +55,7 @@ namespace asst } protected: - virtual bool match_templ(const cv::Mat& templ); + virtual bool match_templ(const cv::Mat templ); std::string m_templ_name; MatchRect m_result; diff --git a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp index fef8ba9470..408f97d447 100644 --- a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp +++ b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp @@ -3,7 +3,7 @@ #include "Logger.hpp" #include "Resource.h" -asst::MultiMatchImageAnalyzer::MultiMatchImageAnalyzer(const cv::Mat& image, const Rect& roi, std::string templ_name, double templ_thres) +asst::MultiMatchImageAnalyzer::MultiMatchImageAnalyzer(const cv::Mat image, const Rect& roi, std::string templ_name, double templ_thres) : AbstractImageAnalyzer(image, roi), m_templ_name(templ_name), m_templ_thres(templ_thres) @@ -16,7 +16,7 @@ bool asst::MultiMatchImageAnalyzer::analyze() Log.trace("MultiMatchImageAnalyzer::analyze | ", m_templ_name); m_result.clear(); - const cv::Mat& templ = Resrc.templ().get_templ(m_templ_name); + const cv::Mat templ = Resrc.templ().get_templ(m_templ_name); if (templ.empty()) { Log.error("templ is empty!"); return false; @@ -39,7 +39,7 @@ void asst::MultiMatchImageAnalyzer::sort_result() }); } -bool asst::MultiMatchImageAnalyzer::multi_match_templ(const cv::Mat& templ) +bool asst::MultiMatchImageAnalyzer::multi_match_templ(const cv::Mat templ) { cv::Mat matched; cv::Mat image_roi = m_image(utils::make_rect(m_roi)); diff --git a/src/MeoAssistant/MultiMatchImageAnalyzer.h b/src/MeoAssistant/MultiMatchImageAnalyzer.h index 4570360712..2dc0bb0cbd 100644 --- a/src/MeoAssistant/MultiMatchImageAnalyzer.h +++ b/src/MeoAssistant/MultiMatchImageAnalyzer.h @@ -7,7 +7,7 @@ namespace asst { public: using AbstractImageAnalyzer::AbstractImageAnalyzer; - MultiMatchImageAnalyzer(const cv::Mat& image, const Rect& roi, std::string templ_name, double templ_thres); + MultiMatchImageAnalyzer(const cv::Mat image, const Rect& roi, std::string templ_name, double templ_thres); virtual ~MultiMatchImageAnalyzer() = default; virtual bool analyze() override; @@ -42,7 +42,7 @@ namespace asst } protected: - virtual bool multi_match_templ(const cv::Mat& templ); + virtual bool multi_match_templ(const cv::Mat templ); std::string m_templ_name; double m_templ_thres = 0.0; diff --git a/src/MeoAssistant/OcrImageAnalyzer.cpp b/src/MeoAssistant/OcrImageAnalyzer.cpp index 0fc6811bc8..579869c678 100644 --- a/src/MeoAssistant/OcrImageAnalyzer.cpp +++ b/src/MeoAssistant/OcrImageAnalyzer.cpp @@ -1,54 +1,54 @@ -#include "OcrImageAnalyzer.h" - -#include - -#include "Logger.hpp" +#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; +#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; @@ -61,9 +61,9 @@ bool asst::OcrImageAnalyzer::analyze() need_local = !AipOcr::get_instance().request_ocr_general(m_image, m_ocr_result, all_pred); } } - if (need_local) { + 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(); + } + //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 9eb18a1346..38c7ea3532 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 56359af8d4..631f506483 100644 --- a/src/MeoAssistant/OcrPack.cpp +++ b/src/MeoAssistant/OcrPack.cpp @@ -31,7 +31,7 @@ bool asst::OcrPack::load(const std::string& dir) return m_ocr != nullptr; } -std::vector asst::OcrPack::recognize(const cv::Mat& image, const asst::TextRectProc& pred, bool without_det) +std::vector asst::OcrPack::recognize(const cv::Mat image, const asst::TextRectProc& pred, bool without_det) { LogTraceFunction; @@ -48,7 +48,7 @@ std::vector asst::OcrPack::recognize(const cv::Mat& image, const memset(*(strs + i), 0, MaxTextSize); } float scores[MaxBoxSize] = { 0 }; - size_t size; + size_t size = 0; Log.trace("Without Det:", without_det); if (!without_det) { @@ -98,7 +98,7 @@ std::vector asst::OcrPack::recognize(const cv::Mat& image, const return result; } -std::vector asst::OcrPack::recognize(const cv::Mat& image, const asst::Rect& roi, const asst::TextRectProc& pred, bool without_det) +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) { diff --git a/src/MeoAssistant/OcrPack.h b/src/MeoAssistant/OcrPack.h index 6e0f953771..2214d077f8 100644 --- a/src/MeoAssistant/OcrPack.h +++ b/src/MeoAssistant/OcrPack.h @@ -21,8 +21,8 @@ namespace asst virtual bool load(const std::string& dir) override; - std::vector recognize(const cv::Mat& image, const TextRectProc& pred = nullptr, bool without_det = false); - std::vector recognize(const cv::Mat& image, const Rect& roi, const TextRectProc& pred = nullptr, bool without_det = false); + std::vector recognize(const cv::Mat image, const TextRectProc& pred = nullptr, bool without_det = false); + std::vector recognize(const cv::Mat image, const Rect& roi, const TextRectProc& pred = nullptr, bool without_det = false); private: paddle_ocr_t* m_ocr = nullptr; diff --git a/src/MeoAssistant/PenguinPack.cpp b/src/MeoAssistant/PenguinPack.cpp index 4a6ee7ddbd..6831d64ccd 100644 --- a/src/MeoAssistant/PenguinPack.cpp +++ b/src/MeoAssistant/PenguinPack.cpp @@ -27,7 +27,7 @@ void asst::PenguinPack::set_language(const std::string& server) penguin::load_server(server.c_str()); } -std::string asst::PenguinPack::recognize(const cv::Mat& image) +std::string asst::PenguinPack::recognize(const cv::Mat image) { std::vector buf; cv::imencode(".png", image, buf); diff --git a/src/MeoAssistant/PenguinPack.h b/src/MeoAssistant/PenguinPack.h index aecb424eb8..d0bf3ce48b 100644 --- a/src/MeoAssistant/PenguinPack.h +++ b/src/MeoAssistant/PenguinPack.h @@ -18,7 +18,7 @@ namespace asst virtual bool load(const std::string& dir) override; void set_language(const std::string& server); - std::string recognize(const cv::Mat& image); + std::string recognize(const cv::Mat image); private: bool load_json(const std::string& stage_path, const std::string& hash_path); diff --git a/src/MeoAssistant/ProcessTask.cpp b/src/MeoAssistant/ProcessTask.cpp index 9ecb5e368b..c00deb31c2 100644 --- a/src/MeoAssistant/ProcessTask.cpp +++ b/src/MeoAssistant/ProcessTask.cpp @@ -1,213 +1,213 @@ -#include "ProcessTask.h" - -#include -#include - -#include - -#include "AsstUtils.hpp" -#include "Controller.h" -#include "PenguinUploader.h" -#include "ProcessTaskImageAnalyzer.h" -#include "Resource.h" -#include "Logger.hpp" - -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; - std::shared_ptr task_info_ptr; - // 如果第一个任务是JustReturn的,那就没必要再截图并计算了 - if (auto front_task_ptr = task.get(m_cur_tasks_name.front()); - front_task_ptr->algorithm == AlgorithmType::JustReturn) { - task_info_ptr = front_task_ptr; - } - else { - const auto& image = Ctrler.get_image(); - ProcessTaskImageAnalyzer analyzer(image, m_cur_tasks_name); - if (!analyzer.analyze()) { - return false; - } - task_info_ptr = analyzer.get_result(); - rect = analyzer.get_rect(); - } - if (need_exit()) { - return false; - } - const auto& res_move = task_info_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[task_info_ptr->name]; - - json::value callback_json = json::object{ - { "name", task_info_ptr->name }, - { "type", static_cast(task_info_ptr->action) }, - { "exec_times", exec_times }, - { "max_times", task_info_ptr->max_times }, - { "task_type", "ProcessTask" }, - { "algorithm", static_cast(task_info_ptr->algorithm) } - }; - m_callback(AsstMsg::TaskMatched, callback_json, m_callback_arg); - - int max_times = task_info_ptr->max_times; - if (auto iter = m_times_limit.find(task_info_ptr->name); - iter != m_times_limit.cend()) { +#include "ProcessTask.h" + +#include +#include + +#include + +#include "AsstUtils.hpp" +#include "Controller.h" +#include "PenguinUploader.h" +#include "ProcessTaskImageAnalyzer.h" +#include "Resource.h" +#include "Logger.hpp" + +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; + std::shared_ptr task_info_ptr; + // 如果第一个任务是JustReturn的,那就没必要再截图并计算了 + if (auto front_task_ptr = task.get(m_cur_tasks_name.front()); + front_task_ptr->algorithm == AlgorithmType::JustReturn) { + task_info_ptr = front_task_ptr; + } + else { + const auto image = Ctrler.get_image(); + ProcessTaskImageAnalyzer analyzer(image, m_cur_tasks_name); + if (!analyzer.analyze()) { + return false; + } + task_info_ptr = analyzer.get_result(); + rect = analyzer.get_rect(); + } + if (need_exit()) { + return false; + } + const auto& res_move = task_info_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[task_info_ptr->name]; + + json::value callback_json = json::object{ + { "name", task_info_ptr->name }, + { "type", static_cast(task_info_ptr->action) }, + { "exec_times", exec_times }, + { "max_times", task_info_ptr->max_times }, + { "task_type", "ProcessTask" }, + { "algorithm", static_cast(task_info_ptr->algorithm) } + }; + m_callback(AsstMsg::TaskMatched, callback_json, m_callback_arg); + + int max_times = task_info_ptr->max_times; + if (auto iter = m_times_limit.find(task_info_ptr->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(task_info_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(task_info_ptr->exceeded_next); - sleep(task_delay); - continue; - } - - // 前置固定延时 - if (!sleep(task_info_ptr->pre_delay)) { - return false; - } - - bool need_stop = false; - switch (task_info_ptr->action) { - case ProcessTaskAction::ClickRect: - rect = task_info_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(task_info_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: { - 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); - - 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); - } - } break; - default: - break; + 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(task_info_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(task_info_ptr->exceeded_next); + sleep(task_delay); + continue; + } + + // 前置固定延时 + if (!sleep(task_info_ptr->pre_delay)) { + return false; + } + + bool need_stop = false; + switch (task_info_ptr->action) { + case ProcessTaskAction::ClickRect: + rect = task_info_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(task_info_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: { + 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); + + 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); + } + } break; + default: + break; } m_cur_retry = 0; - - ++exec_times; - - // 减少其他任务的执行次数 - // 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效 - // 所以要给蓝色开始行动的次数减一 - for (const std::string& reduce : task_info_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); - - // 后置固定延时 - if (!sleep(task_info_ptr->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(task_info_ptr->next); - Log.trace(next_json.to_string()); - - set_tasks(task_info_ptr->next); - sleep(task_delay); - } - - return true; -} - -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; - } + + ++exec_times; + + // 减少其他任务的执行次数 + // 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效 + // 所以要给蓝色开始行动的次数减一 + for (const std::string& reduce : task_info_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); + + // 后置固定延时 + if (!sleep(task_info_ptr->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(task_info_ptr->next); + Log.trace(next_json.to_string()); + + set_tasks(task_info_ptr->next); + sleep(task_delay); + } + + return true; +} + +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 9440cb143f..5ec09be3b7 100644 --- a/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp +++ b/src/MeoAssistant/ProcessTaskImageAnalyzer.cpp @@ -8,7 +8,7 @@ #include "OcrImageAnalyzer.h" #include "Resource.h" -asst::ProcessTaskImageAnalyzer::ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name) +asst::ProcessTaskImageAnalyzer::ProcessTaskImageAnalyzer(const cv::Mat image, std::vector tasks_name) : AbstractImageAnalyzer(image), m_tasks_name(std::move(tasks_name)) { @@ -119,7 +119,7 @@ bool asst::ProcessTaskImageAnalyzer::analyze() return false; } -void asst::ProcessTaskImageAnalyzer::set_image(const cv::Mat & image) +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 886533007e..0df1287194 100644 --- a/src/MeoAssistant/ProcessTaskImageAnalyzer.h +++ b/src/MeoAssistant/ProcessTaskImageAnalyzer.h @@ -16,12 +16,12 @@ namespace asst { public: using AbstractImageAnalyzer::AbstractImageAnalyzer; - ProcessTaskImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; - ProcessTaskImageAnalyzer(const cv::Mat& image, std::vector tasks_name); + 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; + virtual void set_image(const cv::Mat image) override; void set_tasks(std::vector tasks_name) { @@ -45,7 +45,7 @@ namespace asst { AbstractImageAnalyzer::set_roi(roi); } - virtual void set_image(const cv::Mat& image, const Rect& roi) + virtual void set_image(const cv::Mat image, const Rect& roi) { AbstractImageAnalyzer::set_image(image, roi); } diff --git a/src/MeoAssistant/RecruitImageAnalyzer.h b/src/MeoAssistant/RecruitImageAnalyzer.h index 5c79c5f297..00ee3e1794 100644 --- a/src/MeoAssistant/RecruitImageAnalyzer.h +++ b/src/MeoAssistant/RecruitImageAnalyzer.h @@ -9,7 +9,7 @@ namespace asst { public: using AbstractImageAnalyzer::AbstractImageAnalyzer; - RecruitImageAnalyzer(const cv::Mat& image, const Rect& roi) = delete; + RecruitImageAnalyzer(const cv::Mat image, const Rect& roi) = delete; virtual ~RecruitImageAnalyzer() = default; virtual bool analyze() override; @@ -37,7 +37,7 @@ namespace asst { AbstractImageAnalyzer::set_roi(roi); } - virtual void set_image(const cv::Mat& image, const Rect& roi) + virtual void set_image(const cv::Mat image, const Rect& roi) { AbstractImageAnalyzer::set_image(image, roi); } diff --git a/src/MeoAssistant/RecruitTask.cpp b/src/MeoAssistant/RecruitTask.cpp index 04b2261d6f..1a9aaae0ec 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/TaskData.cpp b/src/MeoAssistant/TaskData.cpp index 7ebd1c8934..bea0e55e00 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; +#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 diff --git a/src/MeoAssistant/TemplResource.cpp b/src/MeoAssistant/TemplResource.cpp index 4d1c841600..99b43de2eb 100644 --- a/src/MeoAssistant/TemplResource.cpp +++ b/src/MeoAssistant/TemplResource.cpp @@ -33,7 +33,7 @@ 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 +const cv::Mat asst::TemplResource::get_templ(const std::string& key) const noexcept { if (auto iter = m_templs.find(key); iter != m_templs.cend()) { diff --git a/src/MeoAssistant/TemplResource.h b/src/MeoAssistant/TemplResource.h index 1ff054b473..a8303488b2 100644 --- a/src/MeoAssistant/TemplResource.h +++ b/src/MeoAssistant/TemplResource.h @@ -19,7 +19,7 @@ namespace asst virtual bool load(const std::string& dir) override; bool exist_templ(const std::string& key) const noexcept; - const cv::Mat& get_templ(const std::string& key) const noexcept; + const cv::Mat get_templ(const std::string& key) const noexcept; void emplace_templ(std::string key, cv::Mat templ);