diff --git a/MeoAssistance/include/Assistance.h b/MeoAssistance/include/Assistance.h index e786c5212a..aecda81c96 100644 --- a/MeoAssistance/include/Assistance.h +++ b/MeoAssistance/include/Assistance.h @@ -24,25 +24,18 @@ namespace asst { void stop(); private: - static void identify_function(Assistance* pThis); // 识别线程,生产者 - static void control_function(Assistance* pThis); // 控制线程,消费者 + static void working_proc(Assistance* pThis); std::shared_ptr m_pCtrl = nullptr; std::shared_ptr m_pWindow = nullptr; std::shared_ptr m_pView = nullptr; std::shared_ptr m_Ider = nullptr; - std::queue m_tasks; - std::mutex m_tasks_mutex; - - std::thread m_control_thread; - std::thread m_identify_thread; - bool m_control_exit = false; - bool m_identify_exit = false; - bool m_control_running = false; - bool m_identify_running = false; - std::condition_variable m_control_cv; - std::condition_variable m_identify_cv; + std::thread m_working_thread; + std::mutex m_mutex; + std::condition_variable m_condvar; + bool m_thread_exit = false; + bool m_thread_running = false; }; } \ No newline at end of file diff --git a/MeoAssistance/include/AsstDef.h b/MeoAssistance/include/AsstDef.h index 2581485cf9..19af9d4ab3 100644 --- a/MeoAssistance/include/AsstDef.h +++ b/MeoAssistance/include/AsstDef.h @@ -25,6 +25,7 @@ namespace asst { struct Point { + Point() = default; Point(int x, int y) : x(x), y(y) {} int x = 0; int y = 0; @@ -32,9 +33,19 @@ namespace asst { struct Rect { + Rect() = default; Rect(int x, int y, int width, int height) : x(x), y(y), width(width), height(height) {} - Rect operator*(double rhs) const { return { x, y, static_cast(width * rhs), static_cast(height * rhs) }; } + Rect operator*(double rhs) const + { + return { x, y, static_cast(width * rhs), static_cast(height * rhs) }; + } + Rect center_zoom(double scale) + { + int half_width_scale = static_cast(width * (1- scale) / 2) ; + int half_hight_scale = static_cast(height * (1 - scale) / 2) ; + return { x + half_width_scale, y + half_hight_scale, width - half_width_scale, height - half_hight_scale }; + } int x = 0; int y = 0; int width = 0; @@ -50,7 +61,7 @@ namespace asst { } template - void DebugPrint(const std::string & level, Args &&... args) + void DebugPrint(const std::string& level, Args &&... args) { static std::mutex trace_mutex; std::unique_lock trace_lock(trace_mutex); diff --git a/MeoAssistance/include/Configer.h b/MeoAssistance/include/Configer.h index 5d2c638f50..9070c54572 100644 --- a/MeoAssistance/include/Configer.h +++ b/MeoAssistance/include/Configer.h @@ -14,7 +14,7 @@ namespace asst { static std::string getResDir(); static json::object dataObj; static json::object handleObj; - + static json::object optionsObj; private: Configer() = default; diff --git a/MeoAssistance/include/Identify.h b/MeoAssistance/include/Identify.h index cf8b227653..fb6d3777b6 100644 --- a/MeoAssistance/include/Identify.h +++ b/MeoAssistance/include/Identify.h @@ -4,6 +4,7 @@ #include #include +#include namespace asst { @@ -19,7 +20,10 @@ namespace asst { double imgHistComp(const cv::Mat& lhs, const cv::Mat& rhs); double imgHistComp(const cv::Mat& cur, const std::string& src, asst::Rect compRect); - Rect findImage(const cv::Mat& image, const cv::Mat& templ); + std::pair findImage(const cv::Mat& image, const cv::Mat& templ); + std::pair findImage(const cv::Mat& cur, const std::string& templ); + std::pair findImageWithFile(const cv::Mat& cur, const std::string& filename); + private: std::unordered_map m_matMap; }; diff --git a/MeoAssistance/include/WinMacro.h b/MeoAssistance/include/WinMacro.h index ff71e11422..f87fca9392 100644 --- a/MeoAssistance/include/WinMacro.h +++ b/MeoAssistance/include/WinMacro.h @@ -16,6 +16,7 @@ namespace asst { bool findHandle(); bool resizeWindow(int Width, int Height); + bool resizeWindow(); // by configer bool click(const Point & p); bool clickRange(const Rect & rect); cv::Mat getImage(const Rect& rect); @@ -26,5 +27,9 @@ namespace asst { HandleType m_handle_type; HWND m_handle = NULL; std::minstd_rand m_rand_engine; + int m_width = 0; + int m_height = 0; + int m_xOffset = 0; + int m_yOffset = 0; }; } diff --git a/MeoAssistance/src/Assistance.cpp b/MeoAssistance/src/Assistance.cpp index 5cde83255c..56a9e89862 100644 --- a/MeoAssistance/src/Assistance.cpp +++ b/MeoAssistance/src/Assistance.cpp @@ -16,26 +16,18 @@ Assistance::Assistance() m_Ider->addImage(pair.first, Configer::getResDir() + pair.second["filename"].as_string()); } - m_control_thread = std::thread(control_function, this); - m_identify_thread = std::thread(identify_function, this); + m_working_thread = std::thread(working_proc, this); } Assistance::~Assistance() { - m_control_exit = true; - m_control_running = false; - m_control_cv.notify_all(); + m_thread_exit = true; + m_thread_running = false; + m_condvar.notify_one(); - m_identify_exit = true; - m_identify_running = false; - m_identify_cv.notify_all(); - - if (m_control_thread.joinable()) { - m_control_thread.join(); - } - if (m_identify_thread.joinable()) { - m_identify_thread.join(); + if (m_working_thread.joinable()) { + m_working_thread.join(); } } @@ -43,95 +35,104 @@ bool Assistance::setSimulatorType(SimulatorType type) { stop(); - std::unique_lock lock(m_tasks_mutex); - std::queue empty; - m_tasks.swap(empty); + std::unique_lock lock(m_mutex); int int_type = static_cast(type); m_pCtrl = std::make_shared(static_cast(int_type | static_cast(HandleType::Control))); m_pWindow = std::make_shared(static_cast(int_type | static_cast(HandleType::Window))); m_pView = std::make_shared(static_cast(int_type | static_cast(HandleType::View))); bool ret = m_pCtrl->findHandle() && m_pWindow->findHandle() && m_pView->findHandle(); if (ret) { - m_pWindow->resizeWindow(1200, 720); + ret = m_pWindow->resizeWindow(); } return ret; } void Assistance::start() { - std::unique_lock lock(m_tasks_mutex); + std::unique_lock lock(m_mutex); - m_control_running = true; - m_identify_running = true; - m_control_cv.notify_all(); - m_identify_cv.notify_all(); + m_thread_running = true; + m_condvar.notify_one(); } void Assistance::stop() { - std::unique_lock lock(m_tasks_mutex); + std::unique_lock lock(m_mutex); - m_control_running = false; - m_identify_running = false; + m_thread_running = false; } -void Assistance::identify_function(Assistance* pThis) +void Assistance::working_proc(Assistance* pThis) { - while (!pThis->m_identify_exit) { - std::unique_lock lock(pThis->m_tasks_mutex); - if (pThis->m_identify_running) { + while (!pThis->m_thread_exit) { + std::unique_lock lock(pThis->m_mutex); + if (pThis->m_thread_running) { auto curImg = pThis->m_pView->getImage(pThis->m_pView->getWindowRect()); - std::string matched_task; - double max_similarity = 0; + double max_value = -1; + Rect cor_rect; + std::pair matched; for (auto&& pair : Configer::dataObj) { - double similarity = pThis->m_Ider->imgHistComp(curImg, pair.first, jsonToRect(pair.second["viewRect"].as_array())); - DebugTrace("%-20s %f", pair.first.c_str(), similarity); - if (similarity >= pair.second["similarity"].as_double()) { - if (similarity > max_similarity) { - max_similarity = similarity; - matched_task = pair.first; + auto&& [value, rect] = pThis->m_Ider->findImage(curImg, pair.first); + DebugTrace("%-20s %f", pair.first.c_str(), value); + if (value >= pair.second["threshold"].as_double()) { + if (value >= max_value) { + max_value = value; + cor_rect = rect; + matched = pair; } } } - if (max_similarity != 0) { - DebugTrace("Max: %s, Similarity: %f", matched_task.c_str(), max_similarity); - pThis->m_tasks.emplace(matched_task); + if (max_value >= 0) { + auto task = Configer::dataObj[matched.first].as_object(); + std::string opType = task["type"].as_string(); + DebugTraceInfo("Matched: %s, type: %s", matched.first.c_str(), opType.c_str()); + bool next_loop = true; + while (next_loop) { + if (opType == "clickSelf") { + pThis->m_pCtrl->clickRange(cor_rect); + } + else if (opType == "clickRand") { + pThis->m_pCtrl->clickRange(pThis->m_pCtrl->getWindowRect()); + } + else if (opType == "next") { + json::value nextObj = task["next"]; + std::string name = nextObj["name"].as_string(); + std::string filepath = Configer::getResDir() + nextObj["filename"].as_string(); + auto&& [value, rect] = pThis->m_Ider->findImageWithFile(curImg, filepath); + DebugTrace("Next: %-20s %f", name.c_str(), value); - pThis->m_control_cv.notify_all(); + if (value >= nextObj["threshold"].as_double()) { + DebugTraceInfo("Next matched: %s", name.c_str()); + task = nextObj.as_object(); + opType = nextObj["type"].as_string(); + cor_rect = rect; + max_value = value; + next_loop = true; + continue; + } + else { + DebugTraceInfo("Next not matched: %s", name.c_str()); + } + } + else if (opType == "stop") { + DebugTrace("opType == stop"); + pThis->m_thread_running = false; + break; + } + else if (opType == "doNothing") { + // do nothing + } + else { + DebugTraceError("Unknow option type: %s", opType.c_str()); + } + next_loop = false; + } } - - pThis->m_identify_cv.wait_for(lock, std::chrono::milliseconds(1000)); + pThis->m_condvar.wait_for(lock, std::chrono::milliseconds(Configer::optionsObj["delay"].as_integer())); } else { - pThis->m_identify_cv.wait(lock); - } - } -} - -void Assistance::control_function(Assistance* pThis) -{ - while (!pThis->m_control_exit) { - std::unique_lock lock(pThis->m_tasks_mutex); - if (pThis->m_control_running && !pThis->m_tasks.empty()) { - const std::string task_name = pThis->m_tasks.front(); - pThis->m_tasks.pop(); - lock.unlock(); - - auto task = Configer::dataObj[task_name].as_object(); - std::string opType = task["type"].as_string(); - if (opType == "click") { - pThis->m_pCtrl->clickRange(jsonToRect(task["ctrlRect"].as_array())); - } - else if (opType == "stop") { - DebugTrace("opType == stop"); - pThis->m_control_running = false; - pThis->m_identify_running = false; - continue; - } - } - else { - pThis->m_control_cv.wait(lock); + pThis->m_condvar.wait(lock); } } } \ No newline at end of file diff --git a/MeoAssistance/src/Configer.cpp b/MeoAssistance/src/Configer.cpp index a51a7339b6..c847f1aa55 100644 --- a/MeoAssistance/src/Configer.cpp +++ b/MeoAssistance/src/Configer.cpp @@ -10,6 +10,7 @@ using namespace asst; json::object Configer::dataObj; json::object Configer::handleObj; +json::object Configer::optionsObj; std::string Configer::m_curDir; @@ -33,6 +34,8 @@ bool Configer::reload() auto root = std::move(ret).value(); dataObj = root["data"].as_object(); handleObj = root["handle"].as_object(); + optionsObj = root["options"].as_object(); + return true; } diff --git a/MeoAssistance/src/Identify.cpp b/MeoAssistance/src/Identify.cpp index 98fbd4eddb..5ec457971b 100644 --- a/MeoAssistance/src/Identify.cpp +++ b/MeoAssistance/src/Identify.cpp @@ -44,11 +44,43 @@ double Identify::imgHistComp(const cv::Mat& lhs, const cv::Mat& rhs) double Identify::imgHistComp(const cv::Mat& cur, const std::string& src, asst::Rect compRect) { + if (m_matMap.find(src) == m_matMap.end()) { + return 0; + } cv::Rect cvRect(compRect.x, compRect.y, compRect.width, compRect.height); return imgHistComp(cur(cvRect), m_matMap.at(src)(cvRect)); } -asst::Rect Identify::findImage(const cv::Mat& image, const cv::Mat& templ) +std::pair Identify::findImage(const cv::Mat& image, const cv::Mat& templ) { - return { 0, 0, 0, 0 }; -} \ No newline at end of file + cv::Mat image_hsv; + cv::Mat templ_hsv; + cvtColor(image, image_hsv, COLOR_BGR2HSV); + cvtColor(templ, templ_hsv, COLOR_BGR2HSV); + + Mat matched; + matchTemplate(image_hsv, templ_hsv, matched, cv::TM_CCORR_NORMED); + + double minVal = 0, maxVal = 0; + cv::Point minLoc, maxLoc; + minMaxLoc(matched, &minVal, &maxVal, &minLoc, &maxLoc); + + return { maxVal, asst::Rect(maxLoc.x, maxLoc.y, templ.cols, templ.rows).center_zoom(0.8) }; +} + +std::pair Identify::findImage(const cv::Mat& cur, const std::string& templ) +{ + if (m_matMap.find(templ) == m_matMap.end()) { + return { 0, asst::Rect() }; + } + return findImage(cur, m_matMap.at(templ)); +} + +std::pair Identify::findImageWithFile(const cv::Mat& cur, const std::string& filename) +{ + Mat mat = imread(filename); + if (mat.empty()) { + return { 0, asst::Rect() }; + } + return findImage(cur, mat); +} diff --git a/MeoAssistance/src/WinMacro.cpp b/MeoAssistance/src/WinMacro.cpp index e743cfba31..a51acc7dbd 100644 --- a/MeoAssistance/src/WinMacro.cpp +++ b/MeoAssistance/src/WinMacro.cpp @@ -25,13 +25,17 @@ bool WinMacro::findHandle() json::array handle_arr; switch (m_handle_type) { case HandleType::BlueStacksControl: - handle_arr = Configer::handleObj["BlueStacksControl"].as_array(); + m_xOffset = Configer::handleObj["BlueStacks"]["xOffset"].as_integer(); + m_yOffset = Configer::handleObj["BlueStacks"]["yOffset"].as_integer(); + handle_arr = Configer::handleObj["BlueStacks"]["Control"].as_array(); break; case HandleType::BlueStacksView: - handle_arr = Configer::handleObj["BlueStacksView"].as_array(); + handle_arr = Configer::handleObj["BlueStacks"]["View"].as_array(); break; case HandleType::BlueStacksWindow: - handle_arr = Configer::handleObj["BlueStacksWindow"].as_array(); + m_width = Configer::handleObj["BlueStacks"]["Width"].as_integer(); + m_height = Configer::handleObj["BlueStacks"]["Height"].as_integer(); + handle_arr = Configer::handleObj["BlueStacks"]["Window"].as_array(); break; default: std::cerr << "handle type error! " << static_cast(m_handle_type) << std::endl; @@ -63,6 +67,11 @@ bool WinMacro::resizeWindow(int width, int height) return ::MoveWindow(m_handle, 0, 0, width / getScreenScale(), height / getScreenScale(), true); } +bool WinMacro::resizeWindow() +{ + return resizeWindow(m_width, m_height); +} + double WinMacro::getScreenScale() { static double scale = 0; @@ -103,10 +112,10 @@ bool WinMacro::click(const Point& p) return false; } - int x = p.x / getScreenScale(); - int y = p.y / getScreenScale(); + int x = (p.x + m_xOffset) / getScreenScale(); + int y = (p.y + m_yOffset) / getScreenScale(); - DebugTrace("click: %d, %d", x, y); + DebugTrace("click, raw: %d, %d, cor: %d, %d", p.x, p.y, x, y); LPARAM lparam = MAKELPARAM(x, y); @@ -127,16 +136,17 @@ bool WinMacro::clickRange(const Rect& rect) x = rect.x; } else { - std::poisson_distribution x_rand(rect.width); - x = x_rand(m_rand_engine) + rect.x; + int x_rand = std::poisson_distribution(rect.width / 2)(m_rand_engine); + + x = x_rand + rect.x; } if (rect.height == 0) { y = rect.y; } else { - std::poisson_distribution y_rand(rect.height); - y = y_rand(m_rand_engine) + rect.y; + int y_rand = std::poisson_distribution(rect.height / 2 )(m_rand_engine); + y = y_rand + rect.y; } return click({ x, y }); @@ -147,9 +157,11 @@ Rect WinMacro::getWindowRect() RECT rect; bool ret = ::GetWindowRect(m_handle, &rect); if (!ret) { - return { 0, 0, 0 ,0 }; + return Rect(); } - return Rect{ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top } *getScreenScale(); + return Rect{ rect.left, rect.top, + static_cast((rect.right - rect.left) * getScreenScale()), + static_cast((rect.bottom - rect.top) * getScreenScale()) } ; } cv::Mat WinMacro::getImage(const Rect& rect) diff --git a/README.md b/README.md index 97e1083ed8..7bdd21c5fb 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ A game assistance for Arknights ## 浣跨敤璇存槑 -### alpha_2 鐗堟湰 +### alpha_3 鐗堟湰 1. 浣跨敤钃濆彔妯℃嫙鍣ㄦ墦寮鏄庢棩鏂硅垷锛岃繘鍏ユ湁**钃濊壊鐨勫紑濮嬭鍔ㄦ寜閽**鐨勭晫闈紝鍕句笂浠g悊鎸囨尌 -2. 瑙e帇鍘嬬缉鍖咃紝**浣跨敤绠$悊鍛樻潈闄**锛屾墦寮"Test.exe" +2. 瑙e帇鍘嬬缉鍖咃紝**浣跨敤绠$悊鍛樻潈闄**锛屾墦寮"Sanity.exe" 3. 鐩墠鍙湁鏈鍩烘湰鐨勫姛鑳斤紝鍒峰畬浣撳姏+浣撳姏鑽氨浼氳嚜鍔ㄥ仠浜嗏︹ ## Todo @@ -28,19 +28,20 @@ A game assistance for Arknights - [ ] 鍥惧舰鍖栫晫闈 - [ ] 鍔熻兘 - - [ ] 鏀寔鍓跨伃 + - [x] 鏀寔鍓跨伃 - [ ] 鏀寔鍒锋寚瀹氭鏁 - [ ] 鏀寔浣挎ā鎷熷櫒绐楀彛涓嶅彲瑙 - [x] 鑷姩鍚冧綋鍔涜嵂 - [ ] 鑷姩鍚冪煶澶达紙鏍规嵁璁剧疆锛屾寚瀹氭暟閲忥級 - [ ] 浠g悊澶辫触鐨勬儏鍐 - [ ] 鏀寔鏇村妯℃嫙鍣 - - [ ] 鏀寔绛夌骇鎻愬崌 + - [x] 鏀寔绛夌骇鎻愬崌 - [ ] 鏀寔鍑屾櫒4鐐规洿鏂版暟鎹 - [ ] 淇$敤璁块棶 - [ ] 鍩哄缓鏀惰彍 -- [ ] 绠楁硶 - - [ ] 鏇存崲绠楁硶涓烘壘鍥撅紝鑰屼笉鏄綋鍓嶇殑鍖哄煙鐩镐技搴﹀姣 +- [x] 绠楁硶 + - [x] 鏇存崲绠楁硶涓烘壘鍥撅紝鑰屼笉鏄綋鍓嶇殑鍖哄煙鐩镐技搴﹀姣 + - [ ] 浼樺寲绠楁硶鏁堢巼 ## 鑷磋阿 diff --git a/Tools/Sanity/main.cpp b/Tools/Sanity/main.cpp index 899c75a29c..d4f7af1280 100644 --- a/Tools/Sanity/main.cpp +++ b/Tools/Sanity/main.cpp @@ -6,7 +6,7 @@ int main(int argc, char** argv) Assistance asst; if (!asst.setSimulatorType(SimulatorType::BlueStacks)) { - DebugTraceError("Find Simulator Error"); + DebugTraceError("Can't Find Simulator or Permission denied."); getchar(); return -1; } diff --git a/resource/MedicineConfirm.png b/resource/MedicineConfirm.png new file mode 100644 index 0000000000..45b7e577d8 Binary files /dev/null and b/resource/MedicineConfirm.png differ diff --git a/resource/MissionSucceed.png b/resource/MissionSucceed.png deleted file mode 100644 index 5049e6df6f..0000000000 Binary files a/resource/MissionSucceed.png and /dev/null differ diff --git a/resource/PRTS.png b/resource/PRTS.png index 50b8a5897b..fb360f4f7e 100644 Binary files a/resource/PRTS.png and b/resource/PRTS.png differ diff --git a/resource/StartButton1.png b/resource/StartButton1.png index 8248bc57a2..47d3bf633f 100644 Binary files a/resource/StartButton1.png and b/resource/StartButton1.png differ diff --git a/resource/StartButton2.png b/resource/StartButton2.png index 1e45c4d6c1..00db0efc4c 100644 Binary files a/resource/StartButton2.png and b/resource/StartButton2.png differ diff --git a/resource/UseMedicine.png b/resource/UseMedicine.png index 473057bb35..2f1e6a6962 100644 Binary files a/resource/UseMedicine.png and b/resource/UseMedicine.png differ diff --git a/resource/UseStone.png b/resource/UseStone.png index 70244b59f4..d01350133f 100644 Binary files a/resource/UseStone.png and b/resource/UseStone.png differ diff --git a/resource/config.json b/resource/config.json index d7874f77cb..29b20c6982 100644 --- a/resource/config.json +++ b/resource/config.json @@ -1,112 +1,78 @@ { "version": 0.1, + "options": { + "delay": 2000 + }, "handle": { - "BlueStacksControl": [ - { - "class": "BS2CHINAUI", - "window": "BlueStacks App Player" - }, - { - "class": "BS2CHINAUI", - "window": "HOSTWND" - }, - { - "class": "WindowsForms10.Window.8.app.0.34f5582_r6_ad1", - "window": "BlueStacks Android PluginAndroid" - } - ], - "BlueStacksView": [ - { - "class": "BS2CHINAUI", - "window": "BlueStacks App Player" - } - ], - "BlueStacksWindow": [ - { - "class": "BS2CHINAUI", - "window": "BlueStacks App Player" - } - ] + "BlueStacks": { + "Control": [ + { + "class": "BS2CHINAUI", + "window": "BlueStacks App Player" + }, + { + "class": "BS2CHINAUI", + "window": "HOSTWND" + }, + { + "class": "WindowsForms10.Window.8.app.0.34f5582_r6_ad1", + "window": "BlueStacks Android PluginAndroid" + } + ], + "View": [ + { + "class": "BS2CHINAUI", + "window": "BlueStacks App Player" + } + ], + "Window": [ + { + "class": "BS2CHINAUI", + "window": "BlueStacks App Player" + } + ], + "Width": 1294, + "Height": 773, + "xOffset": -7, + "yOffset": -47 + } }, "data": { + "Random": { + "filename": "", + "threshold": 0, + "type": "clickRand" + }, "UseMedicine": { - "viewRect": [ - 571, - 118, - 621, - 504 - ], "filename": "UseMedicine.png", - "similarity": 0.999, - "type": "click", - "ctrlRect": [ - 994, - 523, - 30, - 17 - ] + "threshold": 0.99, + "type": "next", + "next": { + "name": "MedicineConfirm", + "filename": "MedicineConfirm.png", + "threshold": 0.99, + "type": "clickSelf" + } + }, + "PRTS": { + "filename": "PRTS.png", + "threshold": 0.99, + "type": "doNothing" }, "UseStone": { - "viewRect": [ - 571, - 118, - 621, - 504 - ], "filename": "UseStone.png", - "similarity": 0.999, + "threshold": 0.999, "type": "stop" }, - "MissionSucceed": { - "viewRect": [ - 186, - 521, - 47, - 26 - ], - "filename": "MissionSucceed.png", - "similarity": 0.4, - "type": "click", - "ctrlRect": [ - 977, - 596, - 163, - 29 - ] - }, "StartButton1": { - "viewRect": [ - 968, - 627, - 193, - 70 - ], "filename": "StartButton1.png", - "similarity": 0.99, - "type": "click", - "ctrlRect": [ - 977, - 596, - 163, - 29 - ] + "threshold": 0.99, + "type": "clickSelf" }, "StartButton2": { - "viewRect": [ - 966, - 386, - 126, - 259 - ], "filename": "StartButton2.png", - "similarity": 0.99, - "type": "click", - "ctrlRect": [ - 975, - 353, - 97, - 230 - ] + "threshold": 0.99, + "type": "clickSelf" } } } \ No newline at end of file