diff --git a/.github/ISSUE_TEMPLATE/-----bug-report-.md b/.github/ISSUE_TEMPLATE/-----bug-report-.md index 319fade6b8..b72f6922c8 100644 --- a/.github/ISSUE_TEMPLATE/-----bug-report-.md +++ b/.github/ISSUE_TEMPLATE/-----bug-report-.md @@ -7,10 +7,12 @@ assignees: '' --- - + - + + + - + diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml deleted file mode 100644 index ade970547a..0000000000 --- a/.github/workflows/deploy-docs.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: deploy-docs - -on: - push: - branches: - - master - paths: - - "docs/*" - workflow_dispatch: - -jobs: - deploy-to-azure: - runs-on: ubuntu-latest - name: Build and Deploy Job - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_GENTLE_DESERT_00290F400 }} - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "/docs" # App source code path - api_location: "" # Api source code path - optional - output_location: ".vuepress/dist" # Built app content directory - optional diff --git a/.maabundlerignore b/.maabundlerignore new file mode 100644 index 0000000000..f64e1527ca --- /dev/null +++ b/.maabundlerignore @@ -0,0 +1,8 @@ +libiomp5md.dll +mkldnn.dll +mklml.dll +opencv_world453.dll +paddle_inference.dll +ppocr.dll +aria2c.exe +resource/PaddleOCR/** diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 19627b9e33..1f351f3ae1 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -73,11 +73,13 @@ "SetVersion", "UseCleanArtifact", "UseCleanRelease", + "UseCleanReleaseOta", "UseCommitVersion", "UseMaaChangeLog", "UseMaaCore", "UseMaaDevBundle", "UseMaaLegacyBundle", + "UseMaaLegacyBundleOta", "UseMaaResource", "UseMaaResourceChangeLog", "UsePublishArtifact", @@ -105,11 +107,13 @@ "SetVersion", "UseCleanArtifact", "UseCleanRelease", + "UseCleanReleaseOta", "UseCommitVersion", "UseMaaChangeLog", "UseMaaCore", "UseMaaDevBundle", "UseMaaLegacyBundle", + "UseMaaLegacyBundleOta", "UseMaaResource", "UseMaaResourceChangeLog", "UsePublishArtifact", diff --git a/3rdparty/include/cpp-base64/base64.hpp b/3rdparty/include/cpp-base64/base64.hpp deleted file mode 100644 index 5ae3c6e5b9..0000000000 --- a/3rdparty/include/cpp-base64/base64.hpp +++ /dev/null @@ -1,310 +0,0 @@ -/* - base64.cpp and base64.h - - base64 encoding and decoding with C++. - More information at - https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp - - Version: 2.rc.08 (release candidate) - - Copyright (C) 2004-2017, 2020, 2021 René Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - René Nyffenegger rene.nyffenegger@adp-gmbh.ch - -*/ -#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A -#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A - -#include - -#if __cplusplus >= 201703L -#include -#endif // __cplusplus >= 201703L - -std::string base64_encode (std::string const& s, bool url = false); -std::string base64_encode_pem (std::string const& s); -std::string base64_encode_mime(std::string const& s); - -std::string base64_decode(std::string const& s, bool remove_linebreaks = false); -std::string base64_encode(unsigned char const*, size_t len, bool url = false); - -#if __cplusplus >= 201703L -// -// Interface with std::string_view rather than const std::string& -// Requires C++17 -// Provided by Yannic Bonenberger (https://github.com/Yannic) -// -std::string base64_encode (std::string_view s, bool url = false); -std::string base64_encode_pem (std::string_view s); -std::string base64_encode_mime(std::string_view s); - -std::string base64_decode(std::string_view s, bool remove_linebreaks = false); -#endif // __cplusplus >= 201703L - -#include -#include - - // - // Depending on the url parameter in base64_chars, one of - // two sets of base64 characters needs to be chosen. - // They differ in their last two characters. - // -static const char* base64_chars[2] = { - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "+/", - - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "-_"}; - -static unsigned int pos_of_char(const unsigned char chr) { - // - // Return the position of chr within base64_encode() - // - - if (chr >= 'A' && chr <= 'Z') return chr - 'A'; - else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; - else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; - else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( - else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' - else - // - // 2020-10-23: Throw std::exception rather than const char* - //(Pablo Martin-Gomez, https://github.com/Bouska) - // - throw std::runtime_error("Input is not valid base64-encoded data."); -} - -static std::string insert_linebreaks(std::string str, size_t distance) { - // - // Provided by https://github.com/JomaCorpFX, adapted by me. - // - if (!str.length()) { - return ""; - } - - size_t pos = distance; - - while (pos < str.size()) { - str.insert(pos, "\n"); - pos += distance + 1; - } - - return str; -} - -template -static std::string encode_with_line_breaks(String s) { - return insert_linebreaks(base64_encode(s, false), line_length); -} - -template -static std::string encode_pem(String s) { - return encode_with_line_breaks(s); -} - -template -static std::string encode_mime(String s) { - return encode_with_line_breaks(s); -} - -template -static std::string encode(String s, bool url) { - return base64_encode(reinterpret_cast(s.data()), s.length(), url); -} - -std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { - - size_t len_encoded = (in_len +2) / 3 * 4; - - unsigned char trailing_char = url ? '.' : '='; - - // - // Choose set of base64 characters. They differ - // for the last two positions, depending on the url - // parameter. - // A bool (as is the parameter url) is guaranteed - // to evaluate to either 0 or 1 in C++ therefore, - // the correct character set is chosen by subscripting - // base64_chars with url. - // - const char* base64_chars_ = base64_chars[url]; - - std::string ret; - ret.reserve(len_encoded); - - unsigned int pos = 0; - - while (pos < in_len) { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); - - if (pos+1 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); - - if (pos+2 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); - ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); - } - else { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); - ret.push_back(trailing_char); - } - } - else { - - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); - ret.push_back(trailing_char); - ret.push_back(trailing_char); - } - - pos += 3; - } - - - return ret; -} - -template -static std::string decode(String encoded_string, bool remove_linebreaks) { - // - // decode(…) is templated so that it can be used with String = const std::string& - // or std::string_view (requires at least C++17) - // - - if (encoded_string.empty()) return std::string(); - - if (remove_linebreaks) { - - std::string copy(encoded_string); - - copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); - - return base64_decode(copy, false); - } - - size_t length_of_string = encoded_string.length(); - size_t pos = 0; - - // - // The approximate length (bytes) of the decoded string might be one or - // two bytes smaller, depending on the amount of trailing equal signs - // in the encoded string. This approximation is needed to reserve - // enough space in the string to be returned. - // - size_t approx_length_of_decoded_string = length_of_string / 4 * 3; - std::string ret; - ret.reserve(approx_length_of_decoded_string); - - while (pos < length_of_string) { - // - // Iterate over encoded input string in chunks. The size of all - // chunks except the last one is 4 bytes. - // - // The last chunk might be padded with equal signs or dots - // in order to make it 4 bytes in size as well, but this - // is not required as per RFC 2045. - // - // All chunks except the last one produce three output bytes. - // - // The last chunk produces at least one and up to three bytes. - // - - size_t pos_of_char_1 = pos_of_char(encoded_string[pos+1] ); - - // - // Emit the first output byte that is produced in each chunk: - // - ret.push_back(static_cast( ( (pos_of_char(encoded_string[pos+0]) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); - - if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) - encoded_string[pos+2] != '=' && - encoded_string[pos+2] != '.' // accept URL-safe base 64 strings, too, so check for '.' also. - ) - { - // - // Emit a chunk's second byte (which might not be produced in the last chunk). - // - unsigned int pos_of_char_2 = pos_of_char(encoded_string[pos+2] ); - ret.push_back(static_cast( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); - - if ( ( pos + 3 < length_of_string ) && - encoded_string[pos+3] != '=' && - encoded_string[pos+3] != '.' - ) - { - // - // Emit a chunk's third byte (which might not be produced in the last chunk). - // - ret.push_back(static_cast( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string[pos+3]) )); - } - } - - pos += 4; - } - - return ret; -} - -std::string base64_decode(std::string const& s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); -} - -std::string base64_encode(std::string const& s, bool url) { - return encode(s, url); -} - -std::string base64_encode_pem (std::string const& s) { - return encode_pem(s); -} - -std::string base64_encode_mime(std::string const& s) { - return encode_mime(s); -} - -#if __cplusplus >= 201703L -// -// Interface with std::string_view rather than const std::string& -// Requires C++17 -// Provided by Yannic Bonenberger (https://github.com/Yannic) -// - -std::string base64_encode(std::string_view s, bool url) { - return encode(s, url); -} - -std::string base64_encode_pem(std::string_view s) { - return encode_pem(s); -} - -std::string base64_encode_mime(std::string_view s) { - return encode_mime(s); -} - -std::string base64_decode(std::string_view s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); -} - -#endif // __cplusplus >= 201703L - -#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ \ No newline at end of file diff --git a/CHANGELOG_MAA.md b/CHANGELOG_MAA.md index 98cac33a2b..820f871650 100644 --- a/CHANGELOG_MAA.md +++ b/CHANGELOG_MAA.md @@ -1,9 +1,4 @@ -- 支持美服 (YoStarEN) 的一些基础功能 -- 修复自动战斗干员偶尔部署不上去的问题 -- 修复自动战斗第十章部分地图格子错误 -- 修复自动战斗在编队中的人识别不到的问题 -- 修复肉鸽技能重复开的问题 -- 修复一点模拟器连接问题 -- 修复退出软件时不释放 adb 的问题 -- 修复基建`清流`效率错误 -- 其他一些小优化 +- 大幅优化肉鸽评分表算法及专家系统 +- 界面的一些小调整 + +~~虽然是大版本号,但是并没有什么大的更新,纯粹是版本号不够用了~~ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cb40613e2..b723f42825 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ if (WIN32) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") -endif() +endif () add_compile_options("$<$:/W4;/WX>") add_compile_options("$<$>:-Wall;-Wextra;-Wpedantic;-Werror>") @@ -31,6 +31,7 @@ find_library(PaddleOCR_LIB NAMES ppocr PATHS 3rdparty/lib) if (WIN32) find_library(OpenCV NAMES opencv_world453 PATHS 3rdparty/lib) find_library(ZLIB NAMES zlibstatic PATHS 3rdparty/lib) + target_link_libraries(MeoAssistant ws2_32) else () find_package(OpenCV REQUIRED) find_package(ZLIB REQUIRED) diff --git a/README-en.md b/README-en.md index 9e3da470eb..2827771ff1 100644 --- a/README-en.md +++ b/README-en.md @@ -20,7 +20,7 @@ [中文版本README](README.md) -MAA stands for MAA Assistant Arknights +MAA means MAA Assistant Arknights An Arknights assistant @@ -54,7 +54,7 @@ Talk is cheap. Show me the pictures! ### Basic Settings -1. Follow the [Simulator Support](docs/en/SIMULATOR_SUPPORT.md) to configure your simulator. +1. Follow the [Emulator Support](docs/en/EMULATOR_SUPPORTS.md) to configure your emulator. 2. Unzip the archive file to an **ASCII-only** directory 3. All settings are not changeable after running except `Auto shutdown`. @@ -96,7 +96,7 @@ Please refer to: [FAQ](docs/en/FAQ.md) - Map tile recognition: [Arknights-Tile-Pos](https://github.com/yuanyan3060/Arknights-Tile-Pos) - C++ JSON library: [meojson](https://github.com/MistEO/meojson.git) - C++ operator parser: [calculator](https://github.com/kimwalisch/calculator) -- C++ Base64 encoding/decoding[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64) +- ~~C++ Base64 encoding/decoding[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64)~~ - C++ ZIP library: [zlib](https://github.com/madler/zlib) - C++ Gzip library: [gzip-hpp](https://github.com/mapbox/gzip-hpp) - WPF MVVW framework: [Stylet](https://github.com/canton7/Stylet) diff --git a/README.md b/README.md index 91599c11fe..672167bfce 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ MAA 的意思是 MAA Assistant Arknights ## 外服支持 -- 美服 +- 国际服(美服) 支持基本的刷理智、公招识别功能,请参考 [说明](resource/global/YoStarEN/readme.md) - 其他服 计划适配中…… @@ -105,7 +105,7 @@ MAA 的意思是 MAA Assistant Arknights - 地图格子识别:[Arknights-Tile-Pos](https://github.com/yuanyan3060/Arknights-Tile-Pos) - C++ JSON库:[meojson](https://github.com/MistEO/meojson.git) - C++ 运算符解析器:[calculator](https://github.com/kimwalisch/calculator) -- C++ base64编解码:[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64) +- ~~C++ base64编解码:[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64)~~ - C++ 解压压缩库:[zlib](https://github.com/madler/zlib) - C++ Gzip封装:[gzip-hpp](https://github.com/mapbox/gzip-hpp) - WPF MVVW框架:[Stylet](https://github.com/canton7/Stylet) @@ -149,7 +149,7 @@ MAA 的意思是 MAA Assistant Arknights - [任务流程协议](docs/任务流程协议.md) - [自动抄作业协议](docs/战斗流程协议.md) -### 对连 Git 都不熟悉的超级萌新,请看 +### Github PR 流程简述 [写给萌新的发电全流程](docs/写给萌新的发电全流程.md) diff --git a/docs/en/CONTRIBUTING.md b/docs/en/CONTRIBUTING.md index 54654fc11e..96a82d35b0 100644 --- a/docs/en/CONTRIBUTING.md +++ b/docs/en/CONTRIBUTING.md @@ -13,7 +13,7 @@ 1. Download and install `Visual Studio 2022 Community`. Select `Desktop development with C++` and `.NET Desktop Development`. 5. Double-click to open the file `MeoAssistantArknights.sln`. Visual Studio will load the project automatically. -6. Run a build to test whether the development environment has been configured correctly. Chosse `Release` & `x64`, right-click `MeoAsstGui` to set it as the startup project, and run it. If the build is successful, the `MeoAsstGui` window will appear. You can connect to the simulator in order to confirm again that the development environment has been configured correctly. +6. Run a build to test whether the development environment has been configured correctly. Chosse `Release` & `x64`, right-click `MeoAsstGui` to set it as the startup project, and run it. If the build is successful, the `MeoAsstGui` window will appear. You can connect to the emulator in order to confirm again that the development environment has been configured correctly. 7. Now you are free to contribute to MAA. 8. Remember to commit once you have proper number of changes. Don't forget to write a commit message. 9. After development, push your local changes to the remote (of your repository). diff --git a/docs/en/SIMULATOR_SUPPORT.md b/docs/en/EMULATOR_SUPPORTS.md similarity index 97% rename from docs/en/SIMULATOR_SUPPORT.md rename to docs/en/EMULATOR_SUPPORTS.md index 78b3801146..40e2471462 100644 --- a/docs/en/SIMULATOR_SUPPORT.md +++ b/docs/en/EMULATOR_SUPPORTS.md @@ -1,4 +1,4 @@ -## Simulator Support +## Emulator Supports ### Bluestack @@ -14,7 +14,7 @@ Compatible. - Turn on `Settings` - `Advanced` - `Android Debug Bridge`. - Bluestack Hyper-V port changes frequently. Here is a simple hack: - 1. Find `bluestacks.conf` in the installation location of the simulator. + 1. Find `bluestacks.conf` in the installation location of the emulator. 2. Launch MAA, which generates `gui.json`. Open it. 3. Add a new field `Bluestacks.Config.Path`, with the value of the full path of `bluestacks.conf` (backslash should be escaped like `\\`). 4. LinkStart! @@ -33,7 +33,7 @@ Compatible but: - Requires MAA to "Run as Administrator" to get ADB path and address (since MuMu runs as admin). - You can also fill in the ADB path and address if you do not wish to run as admin. -- It has a chance that MAA may stuck at the main screen and prompt mission failed, which is probably related to the rendering method of MuMu. Recommend to change other simulator. +- It has a chance that MAA may stuck at the main screen and prompt mission failed, which is probably related to the rendering method of MuMu. Recommend to change other emulator. ### MuMu Mobile Game Assistant diff --git a/docs/en/FAQ.md b/docs/en/FAQ.md index 1b708575c3..468ce5aada 100644 --- a/docs/en/FAQ.md +++ b/docs/en/FAQ.md @@ -2,6 +2,9 @@ ### The program crashes immediately when I try to run it +- Possible solution 0: The downloaded archive is incomplete. + - If you are the first time using this software, please do not download the compressed package with the word `OTA` in the file name, this is for incremental update and cannot be used alone. + - If you can't use it after an automatic update, there may be some bugs in the automatic update function, you can try to manually download the complete package and use it again. - Possible solution 1: you are missing some runtime libraries. Please try installing [Visual C++ Redistributable](https://docs.microsoft.com/en/cpp/windows/latest-supported-vc-redist?view=msvc-160#visual-studio-2015-2017-2019-and-2022), [.NET Framework 4.8](https://dotnet.microsoft.com/download/dotnet-framework/net48) and restart the program. - Possible solution 2: your CPU instruction set is incompatible. @@ -12,29 +15,37 @@ ### Connection error/not knowing how to fill in ADB path -Tips: please refer to the [Usage](../README-en.md#Usage) section to ensure that you are using your simulator correctly. +Tips: please refer to the [Usage](../README-en.md#Usage) section to ensure that you are using your emulator correctly. -- Approach 1: find the installation path of your simulator, where there may be a file named `adb.exe` (or something similar, e.g. `nox_adb.exe`, `HD-adb.exe`, `adb_server.exe`, etc., EXE files with `adb`). Simply choose the file in the connection settings of MAA! -- Approach 2: please refer to the [Custom Connection](#custom-connection) section. -- Approach 3: change another simulator, such as [Bluestacks international version](https://www.bluestacks.com/download.html) Nougat 64 bit -- Approach 4: change the code related. You can find the code [here](../src/MeoAsstGui/Helper/WinAdapter.cs) written in C# which is related to the hook on the simulator process and getting ADB path. Feel free to submit PR to us! +#### Approach 1 + +1. Make sure that MAA `Settings` - `Connection Settings` - `adb path` is automatically filled in, if it has been filled in, please ignore this step. If not filled in: + + - Option 1: Find the installation path of your emulator, where there may be a file named `adb.exe` (or something similar, e.g. `nox_adb.exe`, `HD-adb.exe`, `adb_server.exe`, etc., EXE files with `adb`). Simply choose the file in the connection settings of MAA! + - Option 2: Download [adb](https://dl.google.com/android/repository/platform-tools-latest-windows.zip) and unzip it, select the `adb.exe` + +2. Confirm that your connection address is filled in correctly, you can Google what the adb address of the emulator you are using is generally in a format like `127.0.0.1:5555` + +#### Approach 2 + +Change another emulator, such as [Bluestacks international version](https://www.bluestacks.com/download.html) Nougat 64 bit + +#### Approach 3 + +Try restarting your computer ### Wrong Recognition/Program freezes after starting Tip 1: The `Current Stage` of auto battle that costs Sanity requires you to go to the screen with the start button. Please confirm they are not related. Tip 2: Follow the steps below until the problem is solved. -1. Confirm that your simulator is supported in the [List of the Supported Simulators](List of the Supported Simulators.md). +1. Confirm that your emulator is supported in the [List of the Supported Emulators](EMULATOR_SUPPORTS.md). 2. Change the DPI to `320 dpi`. 3. Change the resolution to landscape, `1280 * 720`. -4. Try with another simulator, such as [Bluestacks international version](https://www.bluestacks.com/download.html) Nougat 64 bit. (Please note that you are required to switch on ADB in Bluestack simulator.) +4. Try with another emulator, such as [Bluestacks international version](https://www.bluestacks.com/download.html) Nougat 64 bit. (Please note that you are required to switch on ADB in Bluestack emulator.) 5. Submit an issue to us if the problem still exists. ### Custom Connection - Download [adb](https://dl.google.com/android/repository/platform-tools-latest-windows.zip) and unzip it. -- Go to `Settings` - `Connection Settings`, and select the location of `adb.exe`, fill in the address of ADB (with the format of IP+port, e.g. `127.0.0.1:5555`), and choose the type of your simulator. - -### ADB already exists in the environment variable. Do I stil need to download it? - -You need not do so! Go to `Settings` - `Connection Settings`, and set `ADB path` to `adb.exe`, or other relative path. +- Go to `Settings` - `Connection Settings`, and select the location of `adb.exe`, fill in the address of ADB (with the format of IP+port, e.g. `127.0.0.1:5555`), and choose the type of your emulator. diff --git a/docs/回调消息协议.md b/docs/回调消息协议.md index 2ca0da0344..70ef6a4a4b 100644 --- a/docs/回调消息协议.md +++ b/docs/回调消息协议.md @@ -473,3 +473,6 @@ Todo "data": "{\"@type\":\"@penguin-statistics/depot\",\"items\":[{\"id\":\"2004\",\"have\":4,\"name\":\"高级作战记录\"},{\"id\":\"mod_unlock_token\",\"have\":25,\"name\":\"模组数据块\"},{\"id\":\"2003\",\"have\":20,\"name\":\"中级作战记录\"}]}" } ``` + +- `UnsupportedLevel` + 自动抄作业,不支持的关卡名 diff --git a/docs/常见问题.md b/docs/常见问题.md index dafebe9c96..a2070e6007 100644 --- a/docs/常见问题.md +++ b/docs/常见问题.md @@ -2,6 +2,9 @@ ### 软件一打开就闪退 +- 可能性 0: 下载的压缩包不完整 + - 若您是第一次使用本软件,请不要下载文件名中有 `OTA` 字样的压缩包,这个是用于增量更新的,无法单独使用 + - 若您是在某次自动更新后无法使用了,可能是自动更新功能存在一些 bug, 可以尝试重新手动下载完整的包使用 - 可能性 1: 运行库问题。 请尝试安装 [Visual C++ Redistributable](https://docs.microsoft.com/zh-CN/cpp/windows/latest-supported-vc-redist?view=msvc-160#visual-studio-2015-2017-2019-and-2022)、[.NET Framework 4.8](https://dotnet.microsoft.com/download/dotnet-framework/net48) 后重新运行本软件。 - 可能性 2: CPU 指令集不支持。 @@ -12,7 +15,7 @@ ### 连接错误 -提示 : 请参考 [模拟器支持](模拟器支持.md) 确定您的模拟器是受支持的 +提示 : 请参考 [模拟器支持](模拟器支持.md) 确定您的模拟器是受支持的。如果是蓝叠模拟器,请注意在模拟器设置中打开 adb 调试相关选项 #### 方式一 @@ -24,7 +27,11 @@ #### 方式二 -- 换模拟器,推荐 [蓝叠国际版](https://www.bluestacks.com/download.html) Nougat 64 bit +换模拟器,推荐 [蓝叠国际版](https://www.bluestacks.com/download.html) Nougat 64 bit + +#### 方式三 + +重启(电脑)试试 ### 识别错误/任务开始后一直不动、没有反应 @@ -41,7 +48,3 @@ - 下载 [adb](https://dl.google.com/android/repository/platform-tools-latest-windows.zip) 并解压 - 进入软件 `设置` - `连接设置`,选择 adb.exe 的文件路径、填写 adb 地址(需要填写 IP + 端口,例如 `127.0.0.1:5555` ),并选择你的模拟器类型 - -### 环境变量中已有 adb, 可以不再下载一份么? - -可以!进入软件 `设置` - `连接设置`,将 `adb 路径` 设置为 `adb.exe`, 或其他相对路径即可 diff --git a/docs/肉鸽战斗流程协议.md b/docs/肉鸽战斗流程协议.md index 2f643213ac..cd4d339d76 100644 --- a/docs/肉鸽战斗流程协议.md +++ b/docs/肉鸽战斗流程协议.md @@ -1,65 +1,26 @@ # 肉鸽战斗流程协议 -`resource/roguelike/*.json` 的使用方法及各字段说明 +`resource/roguelike_copilot.json` 的使用方法及各字段说明 ## 完整字段一览 ```jsonc -{ - "stage_name": "驯兽小屋", // 关卡名,必选 - "actions": [ // 战斗中的操作。有序,执行完前一个才会去执行下一个。必选 - { - "type": "部署", // 操作类型,可选,默认 "Deploy" - // "Deploy" | "Skill" | "Retreat" | "AllSkill" - // "部署" | "技能" | "撤退" | "全部技能" - // 中英文皆可,效果相同 - // 若为 "部署", 当费用不够时,会一直等待到费用够 - // 若为 "技能", 当技能 cd 没转好时,会直接跳过当前项。 - // 考虑不同干员技能效果及 cd 差异巨大,谨慎使用(例如 山 2 技能 这种,假如已经开了,你又开一次,就给关了) - // "全部技能", 会一次性使用所有 skill_usgae == 3 的干员的技能 - - "kills": 0, // 击杀数条件,如果没达到就一直等待。可选,默认为 0。直接执行 - // TODO: 其他条件 - // TODO: "condition_type": 0, // 执行条件间的关系,可选,默认 0 - // // 0 - 且; 1 - 或 - - "roles": [ // 该位置使用的职业。可选,默认为空,任何职业都可以 - // 有序,会先使用靠前的职业 - // 会根据位置类型自动区分是高台还是地面。若设置了不可放置的职业,则忽略该职业 - // 如果这里面的职业一个都没有,则会跳过该位置转而执行下一条 - "近卫", - "先锋", - "重装", - "召唤物" - ], - - "waiting_cost": false, // 如果有 roles 中有靠前的职业,但费用不够,是否等待。可选,默认 false - // 为 true 时,会使用 roles 中,当前有的、最靠前的职业(一直等他费用够) - // 为 false 时,会使用 roles 中,当前有的、费用够的中最靠前的职业 - - "location": [ 5, 5 ], // 操作的干员的位置。必选 - - "direction": "左", // 部署干员的干员朝向。 type 为 "部署" 时必选 - // "Left" | "Right" | "Up" | "Down" | "None" - // "左" | "右" | "上" | "下" | "无" - // 中英文皆可,效果相同 - - "pre_delay": 0, // 前置延时。可选,默认 0, 单位毫秒 - "rear_delay": 0, // 后置延时。可选,默认 0, 单位毫秒 - - "timeout": 999999999 // 超时时间。当 type 为 "部署" 时可选。默认 INT_MAX, 单位毫秒 - // 等待超时则放弃当前动作, 转而执行下一个动作 - }, - { // 简单举例 - "location": [6, 6], - "roles": [ - "近卫", - "先锋", - "重装", - "召唤物" - ], - "direction": "左" - } - ] -} +[ + { + "stage_name": "驯兽小屋", // 关卡名,必选 + "replacement_home": [ // 替换掉家门的位置,可选,默认为空 + [ 2, 8 ] // 会使用这里面的坐标来代替蓝门的坐标,然后堵门 + ], + "key_kills": [ // 关键击杀数,可选,默认为空 + 15, // 若不为空,干员技能会攒着,不是好了就放,而是等到击杀数达到时再释放 + ... // 当跑完数组里所有的元素之后,再把干员技能变成好了就放 + ] + }, + { + "stage_name": "巡逻队", + "replacement_home": [ + [ 2, 8 ] + ] + } +] ``` diff --git a/resource/global/YoStarEN/readme.md b/resource/global/YoStarEN/readme.md index 61e0738e3b..015921d2c6 100644 --- a/resource/global/YoStarEN/readme.md +++ b/resource/global/YoStarEN/readme.md @@ -1,8 +1,8 @@ # YoStarEN server -## 美服 +## 国际服(美服) -请进入 `设置` - `启动设置` - `客户端版本` 选择 `悠星美服` +请进入 `设置` - `启动设置` - `客户端版本` 选择 `悠星国际服` 目前支持: diff --git a/resource/global/YoStarEN/resource/tasks.json b/resource/global/YoStarEN/resource/tasks.json index 0f29dd984f..ed69533e39 100644 --- a/resource/global/YoStarEN/resource/tasks.json +++ b/resource/global/YoStarEN/resource/tasks.json @@ -137,6 +137,22 @@ "ReturnToHome" ] }, + "NoFriends": { + "algorithm": "OcrDetect", + "text": [ + "No friends" + ], + "roi": [ + 650, + 100, + 300, + 150 + ], + "action": "DoNothing", + "next": [ + "ReturnToHome" + ] + }, "MallLoading": { "algorithm": "OcrDetect", "action": "DoNothing", @@ -353,5 +369,62 @@ 650 ], "rearDelay": 1000 + }, + "Award": { + "template": "Task.png", + "action": "ClickSelf", + "roi": [ + 650, + 500, + 250, + 170 + ], + "next": [ + "ReceiveAward", + "DailyTask", + "WeeklyTask", + "Award" + ] + }, + "AwardFinished": { + "action": "Stop", + "roi": [ + 400, + 0, + 880, + 150 + ], + "maskRange": [ + 1, + 255 + ] + }, + "DailyTask": { + "action": "ClickSelf", + "cache": false, + "roi": [ + 400, + 0, + 880, + 150 + ], + "next": [ + "ReceiveAward", + "WeeklyTask" + ] + }, + "WeeklyTask": { + "action": "ClickSelf", + "cache": false, + "roi": [ + 400, + 0, + 880, + 150 + ], + "next": [ + "ReceiveAward", + "Stop" + ] } } diff --git a/resource/global/YoStarEN/resource/template/AwardFinished.png b/resource/global/YoStarEN/resource/template/AwardFinished.png new file mode 100644 index 0000000000..089327fa57 Binary files /dev/null and b/resource/global/YoStarEN/resource/template/AwardFinished.png differ diff --git a/resource/global/YoStarEN/resource/template/DailyTask.png b/resource/global/YoStarEN/resource/template/DailyTask.png new file mode 100644 index 0000000000..d39709f605 Binary files /dev/null and b/resource/global/YoStarEN/resource/template/DailyTask.png differ diff --git a/resource/global/YoStarEN/resource/template/FriendsList.png b/resource/global/YoStarEN/resource/template/FriendsList.png new file mode 100644 index 0000000000..eeab4b6802 Binary files /dev/null and b/resource/global/YoStarEN/resource/template/FriendsList.png differ diff --git a/resource/global/YoStarEN/resource/template/ReceiveAward.png b/resource/global/YoStarEN/resource/template/ReceiveAward.png new file mode 100644 index 0000000000..672fc1240e Binary files /dev/null and b/resource/global/YoStarEN/resource/template/ReceiveAward.png differ diff --git a/resource/global/YoStarEN/resource/template/StartToVisit.png b/resource/global/YoStarEN/resource/template/StartToVisit.png new file mode 100644 index 0000000000..f95f83f4de Binary files /dev/null and b/resource/global/YoStarEN/resource/template/StartToVisit.png differ diff --git a/resource/global/YoStarEN/resource/template/Task.png b/resource/global/YoStarEN/resource/template/Task.png new file mode 100644 index 0000000000..c8312c026b Binary files /dev/null and b/resource/global/YoStarEN/resource/template/Task.png differ diff --git a/resource/global/YoStarEN/resource/template/VisitNext.png b/resource/global/YoStarEN/resource/template/VisitNext.png new file mode 100644 index 0000000000..2a94094393 Binary files /dev/null and b/resource/global/YoStarEN/resource/template/VisitNext.png differ diff --git a/resource/global/YoStarEN/resource/template/VisitNextBlack.png b/resource/global/YoStarEN/resource/template/VisitNextBlack.png new file mode 100644 index 0000000000..3a2bf544c9 Binary files /dev/null and b/resource/global/YoStarEN/resource/template/VisitNextBlack.png differ diff --git a/resource/global/YoStarEN/resource/template/WeeklyTask.png b/resource/global/YoStarEN/resource/template/WeeklyTask.png new file mode 100644 index 0000000000..9c46b603d4 Binary files /dev/null and b/resource/global/YoStarEN/resource/template/WeeklyTask.png differ diff --git a/resource/roguelike/Accident.json b/resource/roguelike/Accident.json deleted file mode 100644 index 6bc59c6a9d..0000000000 --- a/resource/roguelike/Accident.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "stage_name": "意外", - "actions": [ - { - "location": [ - 5, - 3 - ], - "roles": [ - "近卫", - "先锋", - "重装", - "召唤物" - ], - "direction": "左" - }, - { - "location": [ - 5, - 2 - ], - "roles": [ - "辅助" - ], - "direction": "左" - } - ] -} diff --git a/resource/roguelike_copilot.json b/resource/roguelike_copilot.json new file mode 100644 index 0000000000..5baa15c176 --- /dev/null +++ b/resource/roguelike_copilot.json @@ -0,0 +1,203 @@ +[ + { + "stage_name": "驯兽小屋", + "replacement_home": [ + [ + 8, + 3 + ] + ] + }, + { + "stage_name": "巡逻队", + "replacement_home": [ + [ + 8, + 3 + ] + ] + }, + { + "stage_name": "压轴登场", + "replacement_home": [ + [ + 2, + 2 + ] + ] + }, + { + "stage_name": "似曾相识", + "replacement_home": [ + [ + 4, + 4 + ], + [ + 5, + 4 + ] + ] + }, + { + "stage_name": "阴云笼罩", + "replacement_home": [ + [ + 7, + 0 + ], + [ + 6, + 0 + ] + ] + }, + { + "stage_name": "从众效应", + "replacement_home": [ + [ + 2, + 4 + ], + [ + 6, + 4 + ], + [ + 3, + 4 + ], + [ + 5, + 4 + ] + ] + }, + { + "stage_name": "恐怖传说", + "replacement_home": [ + [ + 0, + 4 + ] + ] + }, + { + "stage_name": "恐怖传说", + "replacement_home": [ + [ + 0, + 4 + ] + ] + }, + { + "stage_name": "寒渊惜别", + "replacement_home": [ + [ + 10, + 5 + ], + [ + 0, + 1 + ] + ] + }, + { + "stage_name": "正义", + "replacement_home": [ + [ + 6, + 2 + ], + [ + 6, + 3 + ] + ] + }, + { + "stage_name": "仪式之夜", + "replacement_home": [ + [ + 10, + 2 + ], + [ + 0, + 5 + ] + ] + }, + { + "stage_name": "烟花秀", + "replacement_home": [ + [ + 10, + 4 + ], + [ + 10, + 2 + ] + ] + }, + { + "stage_name": "步步紧逼", + "replacement_home": [ + [ + 0, + 2 + ], + [ + 0, + 3 + ] + ] + }, + { + "stage_name": "隔岸观火", + "replacement_home": [ + [ + 11, + 2 + ], + [ + 2, + 5 + ] + ] + }, + { + "stage_name": "斗兽笼", + "replacement_home": [ + [ + 5, + 3 + ] + ] + }, + { + "stage_name": "荒唐把戏", + "replacement_home": [ + [ + 5, + 2 + ] + ] + }, + { + "stage_name": "彻骨冰寒", + "replacement_home": [ + [ + 7, + 4 + ], + [ + 7, + 2 + ] + ] + } +] diff --git a/resource/roguelike_recruit.json b/resource/roguelike_recruit.json index 145dc33263..975ca7b969 100644 --- a/resource/roguelike_recruit.json +++ b/resource/roguelike_recruit.json @@ -37,11 +37,32 @@ "level": 5, "skill": 1 }, + { + "name": "陈", + "level": 6, + "skill": 2 + }, + { + "name": "拉普兰德", + "level": 5, + "skill": 2 + }, + { + "name": "阿米娅", + "level": 5, + "skill": 1 + }, { "name": "史尔特尔", "level": 6, "skill": 2 }, + { + "name": "银灰", + "level": 6, + "skill": 3, + "alternate_skill": 1 + }, { "name": "赫拉格", "level": 6, @@ -53,7 +74,7 @@ "skill": 1 }, { - "name": "拉普兰德", + "name": "幽灵鲨", "level": 5, "skill": 2 }, @@ -62,27 +83,11 @@ "level": 4, "skill": 1 }, - { - "name": "陈", - "level": 6, - "skill": 2 - }, { "name": "斯卡蒂", "level": 6, "skill": 1 }, - { - "name": "阿米娅", - "level": 5, - "skill": 1 - }, - { - "name": "银灰", - "level": 6, - "skill": 3, - "alternate_skill": 1 - }, { "name": "Sharp", "level": 5, @@ -94,7 +99,7 @@ "skill": 1 }, { - "name": "幽灵鲨", + "name": "星极", "level": 5, "skill": 2 }, @@ -105,9 +110,9 @@ "alternate_skill": 2 }, { - "name": "断罪者", - "level": 4, - "skill": 1 + "name": "柏喙", + "level": 5, + "skill": 2 }, { "name": "芳汀", @@ -115,9 +120,9 @@ "skill": 2 }, { - "name": "星极", - "level": 5, - "skill": 2 + "name": "断罪者", + "level": 4, + "skill": 1 }, { "name": "战车", @@ -204,11 +209,6 @@ "level": 5, "skill": 2 }, - { - "name": "柏喙", - "level": 5, - "skill": 2 - }, { "name": "布洛卡", "level": 5, @@ -246,11 +246,6 @@ } ], "Pioneer": [ - { - "name": "芬", - "level": 3, - "skill": 1 - }, { "name": "风笛", "level": 6, @@ -287,6 +282,11 @@ "level": 5, "skill": 2 }, + { + "name": "芬", + "level": 3, + "skill": 1 + }, { "name": "清道夫", "level": 4, @@ -307,6 +307,21 @@ "level": 4, "skill": 1 }, + { + "name": "红豆", + "level": 4, + "skill": 2 + }, + { + "name": "苇草", + "level": 5, + "skill": 1 + }, + { + "name": "格拉尼", + "level": 5, + "skill": 1 + }, { "name": "琴柳", "level": 6, @@ -341,21 +356,6 @@ "name": "翎羽", "level": 3, "skill": 1 - }, - { - "name": "红豆", - "level": 4, - "skill": 2 - }, - { - "name": "苇草", - "level": 5, - "skill": 1 - }, - { - "name": "格拉尼", - "level": 5, - "skill": 1 } ], "Tank": [ @@ -367,8 +367,17 @@ { "name": "塞雷娅", "level": 6, - "skill": 2, - "alternate_skill": 1 + "skill": 2 + }, + { + "name": "暮落", + "level": 5, + "skill": 2 + }, + { + "name": "星熊", + "level": 6, + "skill": 2 }, { "name": "斑点", @@ -401,11 +410,6 @@ "level": 5, "skill": 1 }, - { - "name": "星熊", - "level": 6, - "skill": 2 - }, { "name": "雷蛇", "level": 5, @@ -461,11 +465,6 @@ "level": 4, "skill": 2 }, - { - "name": "暮落", - "level": 5, - "skill": 2 - }, { "name": "极光", "level": 5, @@ -713,11 +712,6 @@ "level": 6, "skill": 3 }, - { - "name": "伊芙利特", - "level": 6, - "skill": 2 - }, { "name": "阿米娅", "level": 5, @@ -733,6 +727,11 @@ "level": 6, "skill": 1 }, + { + "name": "伊芙利特", + "level": 6, + "skill": 2 + }, { "name": "天火", "level": 5, @@ -998,11 +997,6 @@ } ], "Medic": [ - { - "name": "安赛尔", - "level": 3, - "skill": 1 - }, { "name": "凯尔希", "level": 6, @@ -1090,6 +1084,11 @@ "level": 5, "skill": 1 }, + { + "name": "安赛尔", + "level": 3, + "skill": 1 + }, { "name": "闪灵", "level": 6, @@ -1122,22 +1121,12 @@ } ], "Support": [ - { - "name": "浊心斯卡蒂", - "level": 6, - "skill": 2 - }, { "name": "令", "level": 6, "skill": 3, "alternate_skill": 2 }, - { - "name": "梓兰", - "level": 3, - "skill": 1 - }, { "name": "波登可", "level": 4, @@ -1148,16 +1137,17 @@ "level": 6, "skill": 1 }, - { - "name": "安洁莉娜", - "level": 6, - "skill": 1 - }, { "name": "铃兰", "level": 6, "skill": 2 }, + { + "name": "安洁莉娜", + "level": 6, + "skill": 3, + "alternate_skill": 2 + }, { "name": "巫恋", "level": 5, @@ -1178,6 +1168,16 @@ "level": 5, "skill": 1 }, + { + "name": "梓兰", + "level": 3, + "skill": 1 + }, + { + "name": "浊心斯卡蒂", + "level": 6, + "skill": 2 + }, { "name": "月禾", "level": 5, diff --git a/resource/tasks.json b/resource/tasks.json index ce8912fc4f..c7c8e62447 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -203,8 +203,11 @@ "action": "DoNothing", "sub": [ "LE-Open", - "LEChapterToLE", - "StageLE-5" + "LEChapterToLE" + ], + "next": [ + "StageLE-5", + "SwapToStageLE-5" ] }, "LE-6": { @@ -212,8 +215,11 @@ "action": "DoNothing", "sub": [ "LE-Open", - "LEChapterToLE", - "StageLE-6" + "LEChapterToLE" + ], + "next": [ + "StageLE-6", + "SwapToStageLE-6" ] }, "LE-7": { @@ -221,22 +227,21 @@ "action": "DoNothing", "sub": [ "LE-Open", - "LEChapterToLE", - "StageLE-7" + "LEChapterToLE" + ], + "next": [ + "StageLE-7", + "SwapToStageLE-7" ] }, "LE-Open": { - "algorithm": "OcrDetect", - "text": [ - "尘影余音" - ], + "action": "ClickSelf", "roi": [ + 471, 0, - 500, - 500, - 150 - ], - "action": "ClickSelf" + 530, + 711 + ] }, "LEChapterToLE": { "algorithm": "OcrDetect", @@ -252,8 +257,31 @@ ], "action": "ClickSelf", "sub": [ - "SwipeToTheRight", - "SlowlySwipeToTheLeft" + "SwipeToTheRight" + ] + }, + "SwapToStageLE-5": { + "algorithm": "JustReturn", + "action": "SlowlySwipeToTheLeft", + "next": [ + "StageLE-5", + "SwapToStageLE-5" + ] + }, + "SwapToStageLE-6": { + "algorithm": "JustReturn", + "action": "SlowlySwipeToTheLeft", + "next": [ + "StageLE-6", + "SwapToStageLE-6" + ] + }, + "SwapToStageLE-7": { + "algorithm": "JustReturn", + "action": "SlowlySwipeToTheLeft", + "next": [ + "StageLE-7", + "SwapToStageLE-7" ] }, "StageLE-5": { @@ -645,6 +673,10 @@ "^上$", "山" ], + [ + "^云$", + "山" + ], [ "姜哦", "嵯峨" @@ -734,19 +766,15 @@ "狮蝎" ], [ - "第草", - "苇草" + "扬师虫", + "狮蝎" ], [ "漂冬", "凛冬" ], [ - "韦草", - "苇草" - ], - [ - "笔草", + ".+草", "苇草" ], [ @@ -768,6 +796,10 @@ [ "研", "砾" + ], + [ + "紫雨", + "絮语" ] ] }, @@ -2146,7 +2178,8 @@ "WeeklyTask", "AwardFinished", "CloseTaskAwardDouble" - ] + ], + "maxTimes": 10 }, "AwardFinished": { "action": "Stop", @@ -4460,19 +4493,19 @@ "落.+骑士", "落魄骑士" ], - [ - ".+爵的戏.+", - "鸭爵的戏剧" - ], [ "邪异因笼", "邪异囚笼" + ], + [ + "寒洲惜别", + "寒渊惜别" ] ], "roi": [ - 420, - 430, - 440, + 250, + 435, + 800, 100 ] }, @@ -4671,7 +4704,8 @@ 20, 60, 60 - ] + ], + "rearDelay": 100 }, "BattleCancelSelection": { "algorithm": "JustReturn", @@ -4742,6 +4776,7 @@ 210, 118 ], + "templThreshold": 0.7, "cache": false, "action": "ClickSelf", "rectMove": [ @@ -4750,7 +4785,7 @@ 90, 90 ], - "rearDelay": 200 + "rearDelay": 300 }, "BattleSkillStopOnClick": { "roi": [ @@ -4767,7 +4802,7 @@ 90, 90 ], - "rearDelay": 200 + "rearDelay": 300 }, "BattleUseOper": { "algorithm": "JustReturn", @@ -4777,7 +4812,7 @@ }, "BattleSwipeOper": { "algorithm": "JustReturn", - "preDelay": 15000, + "preDelay": 500, "rearDelay": 100, "Doc": "pre 是将干员滑动到场上的 duration 系数;rear 是设置干员朝向滑动的 duration" }, @@ -5056,19 +5091,15 @@ ] }, "Roguelike1BattleSpeedUp": { - "template": "BattleSpeedUp.png", - "action": "ClickSelf", - "roi": [ - 1025, - 0, - 149, - 153 + "algorithm": "JustReturn", + "action": "ClickRect", + "specificRect": [ + 1070, + 20, + 60, + 60 ], - "maskRange": [ - 100, - 255 - ], - "templThreshold": 0.95 + "preDelay": 3000 }, "BattleSkillReady": { "roi": [ @@ -5462,13 +5493,16 @@ "Roguelike1LastRewardRand", "Roguelike1Team3", "Roguelike1CloseCollection", - "Roguelike1DialogSkip" + "Roguelike1DialogSkip", + "Roguelike1StageTrader", + "Roguelike1StageSafeHouse", + "Roguelike1StageEncounter", + "Roguelike1StageBoons", + "Roguelike1StageCambatDps", + "Roguelike1StageEmergencyDps", + "Roguelike1StageDreadfulFoe" ] }, - "BattleWaitingToLoad": { - "algorithm": "JustReturn", - "rearDelay": 200 - }, "Roguelike1CloseCollection": { "action": "ClickSelf", "cache": false, @@ -5523,7 +5557,7 @@ 154 ], "next": [ - "Roguelike1ChooseOper", + "Roguelike1ChooseOperFlag", "Roguelike1RecruitCloseGuide" ], "rearDelay": 1000 @@ -5539,7 +5573,7 @@ 154 ], "next": [ - "Roguelike1ChooseOper", + "Roguelike1ChooseOperFlag", "Roguelike1RecruitCloseGuide" ], "rearDelay": 1000 @@ -5553,7 +5587,7 @@ 134 ], "next": [ - "Roguelike1ChooseOper" + "Roguelike1ChooseOperFlag" ] }, "Roguelike1ChooseOper": { @@ -6312,7 +6346,8 @@ "maskRange": [ 1, 255 - ] + ], + "templThreshold": 0.7 }, "Roguelike1FormationOperSelected": { "template": "empty.png", @@ -6386,15 +6421,16 @@ ] }, "Roguelike1InBattleFlag": { - "algorithm": "OcrDetect", - "text": [ - "角色" - ], + "template": "BattleOfficiallyBegin.png", "roi": [ - 900, - 500, - 380, - 150 + 1165, + 20, + 65, + 65 + ], + "maskRange": [ + 100, + 255 ], "action": "DoNothing", "rearDelay": 5000, @@ -6455,6 +6491,7 @@ "Roguelike1GetDrop1", "Roguelike1GetDrop2", "Roguelike1GetDrop3", + "Roguelike1GetDropSelect", "Roguelike1GetDropRecruit", "Roguelike1GetDropLeave", "Roguelike1StageTrader", @@ -6532,9 +6569,54 @@ ], "templThreshold": 0.85, "rearDelay": 1000, + "next": [ + "Roguelike1ChooseOperFlag" + ] + }, + "Roguelike1GetDropSelect": { + "Doc": "“选择”按钮,有可能是招募二选一,也有收藏品二选一", + "action": "ClickSelf", + "cache": false, + "roi": [ + 0, + 471, + 1280, + 157 + ], + "rearDelay": 2000, + "next": [ + "Roguelike1GetDropSelectRecruit", + "Roguelike1GetDropSelectReward" + ] + }, + "Roguelike1GetDropSelectRecruit": { + "template": "Roguelike1GetDropRecruit.png", + "action": "ClickSelf", + "cache": false, + "roi": [ + 0, + 420, + 1280, + 175 + ], + "templThreshold": 0.85, "next": [ "Roguelike1ChooseOperFlag", - "Roguelike1ChooseOper" + "Roguelike1GetDropSelectRecruit" + ] + }, + "Roguelike1GetDropSelectReward": { + "cache": false, + "action": "ClickSelf", + "roi": [ + 0, + 420, + 1280, + 175 + ], + "next": [ + "Roguelike1ClickToDrops", + "Roguelike1GetDropSelectReward" ] }, "Roguelike1GetDropCompleted": { @@ -6740,9 +6822,9 @@ "templThreshold_Doc": "这里用来作为字间距阈值", "roi": [ 62, - 89, + 90, 43, - 23 + 22 ], "maskRange": [ 150, diff --git a/resource/template/LE-Open.png b/resource/template/LE-Open.png new file mode 100644 index 0000000000..9414c04226 Binary files /dev/null and b/resource/template/LE-Open.png differ diff --git a/resource/template/Roguelike1GetDropSelect.png b/resource/template/Roguelike1GetDropSelect.png new file mode 100644 index 0000000000..23369beb65 Binary files /dev/null and b/resource/template/Roguelike1GetDropSelect.png differ diff --git a/resource/template/Roguelike1GetDropSelectReward.png b/resource/template/Roguelike1GetDropSelectReward.png new file mode 100644 index 0000000000..4a092d18c3 Binary files /dev/null and b/resource/template/Roguelike1GetDropSelectReward.png differ diff --git a/src/MeoAssistant/AbstractConfiger.cpp b/src/MeoAssistant/AbstractConfiger.cpp index 21856c790a..a6195cdc4c 100644 --- a/src/MeoAssistant/AbstractConfiger.cpp +++ b/src/MeoAssistant/AbstractConfiger.cpp @@ -40,5 +40,4 @@ bool asst::AbstractConfiger::load(const std::string& filename) m_last_error = std::string("json field error ") + e.what(); return false; } - return true; } diff --git a/src/MeoAssistant/AbstractImageAnalyzer.cpp b/src/MeoAssistant/AbstractImageAnalyzer.cpp index 18ec33a561..06bd686dbd 100644 --- a/src/MeoAssistant/AbstractImageAnalyzer.cpp +++ b/src/MeoAssistant/AbstractImageAnalyzer.cpp @@ -19,8 +19,7 @@ asst::AbstractImageAnalyzer::AbstractImageAnalyzer(const cv::Mat& image, const R , m_image_draw(image.clone()) #endif -{ -} +{} void asst::AbstractImageAnalyzer::set_image(const cv::Mat image) { @@ -47,7 +46,7 @@ asst::Rect asst::AbstractImageAnalyzer::empty_rect_to_full(const Rect& rect, con return rect; } if (rect.empty()) { - return {0, 0, image.cols, image.rows}; + return { 0, 0, image.cols, image.rows }; } Rect res = rect; diff --git a/src/MeoAssistant/AbstractTask.cpp b/src/MeoAssistant/AbstractTask.cpp index 933aa3c0bd..8bb9b8ba90 100644 --- a/src/MeoAssistant/AbstractTask.cpp +++ b/src/MeoAssistant/AbstractTask.cpp @@ -20,13 +20,12 @@ AbstractTask::AbstractTask(AsstCallback callback, void* callback_arg, std::strin : m_callback(std::move(callback)), m_callback_arg(callback_arg), m_task_chain(std::move(task_chain)) -{ -} +{} bool asst::AbstractTask::run() { if (!m_enable) { - Log.info("task is disable, pass", basic_info().to_string()); + Log.info("task disabled, pass", basic_info().to_string()); return true; } callback(AsstMsg::SubTaskStart, basic_info()); @@ -171,12 +170,12 @@ bool AbstractTask::save_image(const cv::Mat& image, const std::string& dir) bool asst::AbstractTask::need_exit() const { - return m_exit_flag != nullptr && *m_exit_flag == true; + return m_exit_flag != nullptr && *m_exit_flag; } void asst::AbstractTask::callback(AsstMsg msg, const json::value& detail) { - for (TaskPluginPtr plugin : m_plugins) { + for (const TaskPluginPtr& plugin : m_plugins) { plugin->set_exit_flag(m_exit_flag); plugin->set_ctrler(m_ctrler); plugin->set_task_id(m_task_id); diff --git a/src/MeoAssistant/AbstractTask.h b/src/MeoAssistant/AbstractTask.h index 6d768ab051..1daef105ec 100644 --- a/src/MeoAssistant/AbstractTask.h +++ b/src/MeoAssistant/AbstractTask.h @@ -67,7 +67,7 @@ namespace asst json::value basic_info_with_what(std::string what) const; bool sleep(unsigned millisecond); bool need_exit() const; - bool save_image(const cv::Mat& image, const std::string& dir); + static bool save_image(const cv::Mat& image, const std::string& dir); bool m_enable = true; bool m_ignore_error = true; diff --git a/src/MeoAssistant/AipOcr.cpp b/src/MeoAssistant/AipOcr.cpp deleted file mode 100644 index 3d43a91161..0000000000 --- a/src/MeoAssistant/AipOcr.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "AipOcr.h" - -#include - -#include -#include -#include - -#include "AsstUtils.hpp" -#include "Logger.hpp" -#include "Resource.h" - -bool asst::AipOcr::request_access_token(const std::string& client_id, const std::string& client_secret) -{ - LogTraceFunction; - - m_access_token.clear(); - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s")"; - - constexpr int cmd_len = 256; - char cmd[cmd_len] = { 0 }; -#ifdef _MSC_VER - sprintf_s(cmd, cmd_len, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); -#else - sprintf(cmd, cmd_fmt.data(), client_id.c_str(), client_secret.c_str()); -#endif - - Log.trace("call cmd:", cmd); - - std::string response = utils::callcmd(cmd); - - Log.trace("response:", response); - - auto parse_res = json::parse(response); - if (!parse_res) { - return false; - } - try { - auto json = parse_res.value(); - m_access_token = json.at("access_token").as_string(); - } - catch (...) { - return false; - } - return true; -} - -bool asst::AipOcr::request_ocr_general(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) -{ - LogTraceFunction; - - if (m_access_token.empty()) { - auto& cfg = Resrc.cfg().get_options().aip_ocr; - request_access_token(cfg.client_id, cfg.client_secret); - } - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/general?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; - - return request_ocr_and_parse(cmd_fmt, image, out_result, pred); -} - -bool asst::AipOcr::request_ocr_accurate(const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) -{ - LogTraceFunction; - - if (m_access_token.empty()) { - auto& cfg = Resrc.cfg().get_options().aip_ocr; - request_access_token(cfg.client_id, cfg.client_secret); - } - - std::string_view cmd_fmt = - R"(curl -s -k "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=%s" --data "image=%s" -H "Content-Type:application/x-www-form-urlencoded")"; - - return request_ocr_and_parse(cmd_fmt, image, out_result, pred); -} - -bool asst::AipOcr::request_ocr_and_parse(std::string_view cmd_fmt, const cv::Mat& image, std::vector& out_result, const TextRectProc& pred) -{ - if (m_access_token.empty()) { - return false; - } - // CreateProcess 最大只能接受 32767 chars,一张图片随便就超过了 - // 这个功能暂时没法用,得集成下 libcurl 或者别的 - std::vector buf; - cv::imencode(".png", image, buf); - auto* enc_msg = reinterpret_cast(buf.data()); - std::string encoded = base64_encode(enc_msg, buf.size()); - - size_t cmd_len = encoded.size() + cmd_fmt.size() + m_access_token.size(); - auto cmd = new char[cmd_len]; - memset(cmd, 0, cmd_len); -#ifdef _MSC_VER - sprintf_s(cmd, cmd_len, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); -#else - sprintf(cmd, cmd_fmt.data(), m_access_token.c_str(), encoded.c_str()); -#endif - Log.trace("call cmd:", cmd); - std::string response = utils::callcmd(cmd); - delete[] cmd; - Log.trace("response:", response); - - auto parse_res = json::parse(response); - if (!parse_res) { - return false; - } - try { - auto json = parse_res.value(); - return parse_response(json, out_result, pred); - } - catch (...) { - return false; - } -} - -bool asst::AipOcr::parse_response(const json::value& json, std::vector& out_result, const TextRectProc& pred) -{ - if (!json.contains("words_result")) { - return false; - } - - std::vector result; - std::string log_str_raw; - std::string log_str_proc; - for (const auto& word : json.at("words_result").as_array()) { - std::string text = word.at("words").as_string(); - - auto& loc = word.at("location").as_object(); - int left = loc.at("left").as_integer(); - int right = loc.at("right").as_integer(); - int top = loc.at("top").as_integer(); - int bottom = loc.at("bottom").as_integer(); - - Rect rect(left, top, right - left, bottom - top); - - TextRect tr{ 0, rect, text }; - - log_str_raw += tr.to_string() + ", "; - if (!pred || pred(tr)) { - log_str_proc += tr.to_string() + ", "; - result.emplace_back(std::move(tr)); - } - } - - Log.trace("AipOcr::parse_response | raw : ", log_str_raw); - Log.trace("AipOcr::parse_response | proc : ", log_str_proc); - - out_result = result; - return true; -} diff --git a/src/MeoAssistant/AipOcr.h b/src/MeoAssistant/AipOcr.h deleted file mode 100644 index 90f2c87a77..0000000000 --- a/src/MeoAssistant/AipOcr.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -#include "AsstTypes.h" - -namespace cv -{ - class Mat; -} -namespace json -{ - class value; -} - -namespace asst -{ - class AipOcr - { - public: - ~AipOcr() = default; - - static AipOcr& get_instance() - { - static AipOcr unique_instance; - return unique_instance; - } - - 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); - - 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 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 53a4c3de74..0d490b5129 100644 --- a/src/MeoAssistant/Assistant.cpp +++ b/src/MeoAssistant/Assistant.cpp @@ -61,7 +61,6 @@ bool asst::Assistant::connect(const std::string& adb_path, const std::string& ad { LogTraceFunction; - m_inited = false; std::unique_lock lock(m_mutex); stop(false); @@ -70,7 +69,6 @@ bool asst::Assistant::connect(const std::string& adb_path, const std::string& ad if (ret) { m_uuid = m_ctrler->get_uuid(); } - m_inited = ret; return ret; } @@ -85,46 +83,30 @@ asst::Assistant::TaskId asst::Assistant::append_task(const std::string& type, co std::shared_ptr ptr = nullptr; - if (type == FightTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == StartUpTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == AwardTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == VisitTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == MallTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == InfrastTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == RecruitTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == RoguelikeTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == CopilotTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } - else if (type == DepotTask::TaskType) { - ptr = std::make_shared(task_callback, static_cast(this)); - } +#define ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(TASK) \ +else if (type == TASK::TaskType) { ptr = std::make_shared(task_callback, static_cast(this)); } + + if (false) {} + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(FightTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(StartUpTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(AwardTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(VisitTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(MallTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(InfrastTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(RecruitTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(RoguelikeTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(CopilotTask) + ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH(DepotTask) #ifdef ASST_DEBUG - else if (type == DebugTask::TaskType) { - ptr = std::make_shared(task_callback, (void*)this); - } + ASST_ASSISTANT_APPEND_TASK_FROM_TYPE_IF_BRANCH(DebugTask) #endif else { Log.error(__FUNCTION__, "| invalid type:", type); return 0; } +#undef ASST_ASSISTANT_APPEND_TASK_FROM_STRING_IF_BRANCH + bool params_ret = ptr->set_params(ret.value()); if (!params_ret) { return 0; @@ -165,7 +147,7 @@ bool asst::Assistant::set_task_params(TaskId task_id, const std::string& params) std::vector asst::Assistant::get_image() const { - if (!m_inited) { + if (!inited()) { return {}; } return m_ctrler->get_image_encode(); @@ -173,7 +155,7 @@ std::vector asst::Assistant::get_image() const bool asst::Assistant::ctrler_click(int x, int y, bool block) { - if (!m_inited) { + if (!inited()) { return false; } m_ctrler->click(Point(x, y), block); @@ -185,7 +167,7 @@ bool asst::Assistant::start(bool block) LogTraceFunction; Log.trace("Start |", block ? "block" : "non block"); - if (!m_thread_idle || !m_inited) { + if (!m_thread_idle || !inited()) { return false; } std::unique_lock lock; @@ -329,3 +311,8 @@ void Assistant::clear_cache() m_status->clear_rect(); m_status->clear_str(); } + +bool asst::Assistant::inited() const noexcept +{ + return m_ctrler && m_ctrler->inited(); +} diff --git a/src/MeoAssistant/Assistant.h b/src/MeoAssistant/Assistant.h index a498d23ccf..f0f8764853 100644 --- a/src/MeoAssistant/Assistant.h +++ b/src/MeoAssistant/Assistant.h @@ -52,6 +52,7 @@ namespace asst void append_callback(AsstMsg msg, json::value detail); void clear_cache(); + bool inited() const noexcept; bool m_inited = false; std::string m_uuid; diff --git a/src/MeoAssistant/AsstBattleDef.h b/src/MeoAssistant/AsstBattleDef.h index 88af5c5bd6..ff6ae6a938 100644 --- a/src/MeoAssistant/AsstBattleDef.h +++ b/src/MeoAssistant/AsstBattleDef.h @@ -104,22 +104,12 @@ namespace asst std::string name; size_t index; bool cooling = false; - bool operator==(const std::string& oper_name) const noexcept - { - return name == oper_name; - } }; - struct RoguelikeBattleAction + struct RoguelikeBattleData { - int kills = 0; - BattleActionType type = BattleActionType::Deploy; - std::vector roles; - bool waiting_cost = false; - Point location; - BattleDeployDirection direction = BattleDeployDirection::Right; - int pre_delay = 0; - int rear_delay = 0; - int time_out = INT_MAX; + std::string stage_name; + std::vector replacement_home; + std::vector key_kills; }; } diff --git a/src/MeoAssistant/AsstInfrastDef.h b/src/MeoAssistant/AsstInfrastDef.h index d9dc871c90..da492022d8 100644 --- a/src/MeoAssistant/AsstInfrastDef.h +++ b/src/MeoAssistant/AsstInfrastDef.h @@ -2,53 +2,50 @@ #include "AsstTypes.h" -namespace asst +namespace asst::infrast { - namespace infrast + struct Facility { - struct Facility - { - ::std::string id; - ::std::vector<::std::string> products; - int max_num_of_opers = 0; - }; + ::std::string id; + ::std::vector<::std::string> products; + int max_num_of_opers = 0; + }; - enum class SmileyType - { - Invalid = -1, - Rest, // 休息完成,绿色笑脸 - Work, // 工作中,黄色笑脸 - Distract // 注意力涣散,红色哭脸 - }; - struct Smiley - { - SmileyType type = SmileyType::Invalid; - Rect rect; - }; - enum class Doing // 正在做什么 - { - Invalid = -1, - Nothing, // 什么都不在做,也不在休息也不在工作 - Resting, // 休息中 - Working // 工作中 - }; + enum class SmileyType + { + Invalid = -1, + Rest, // 休息完成,绿色笑脸 + Work, // 工作中,黄色笑脸 + Distract // 注意力涣散,红色哭脸 + }; + struct Smiley + { + SmileyType type = SmileyType::Invalid; + Rect rect; + }; + enum class Doing // 正在做什么 + { + Invalid = -1, + Nothing, // 什么都不在做,也不在休息也不在工作 + Resting, // 休息中 + Working // 工作中 + }; - struct Skill - { - ::std::string id; - ::std::string templ_name; - ::std::vector<::std::string> names; // 很多基建技能是一样的,就是名字不同。所以一个技能id可能对应多个名字 - ::std::string desc; - ::std::unordered_map<::std::string, double> efficient; // 技能效率,key:产品名(赤金、经验书等), value: 效率数值 - ::std::unordered_map<::std::string, ::std::string> efficient_regex; // 技能效率正则,key:产品名(赤金、经验书等), value: 效率正则。如不为空,会先对正则进行计算,再加上efficient里面的值 - int max_num = INT_MAX; // 最多选几个该技能 + struct Skill + { + ::std::string id; + ::std::string templ_name; + ::std::vector<::std::string> names; // 很多基建技能是一样的,就是名字不同。所以一个技能id可能对应多个名字 + ::std::string desc; + ::std::unordered_map<::std::string, double> efficient; // 技能效率,key:产品名(赤金、经验书等), value: 效率数值 + ::std::unordered_map<::std::string, ::std::string> efficient_regex; // 技能效率正则,key:产品名(赤金、经验书等), value: 效率正则。如不为空,会先对正则进行计算,再加上efficient里面的值 + int max_num = INT_MAX; // 最多选几个该技能 - bool operator==(const Skill& skill) const noexcept - { - return id == skill.id; - } - }; - } + bool operator==(const Skill& skill) const noexcept + { + return id == skill.id; + } + }; } namespace std @@ -64,67 +61,64 @@ namespace std }; } -namespace asst +namespace asst::infrast { - namespace infrast + struct BattleRealTimeOper { - struct BattleRealTimeOper - { - ::std::string face_hash; // 有些干员的技能是完全一样的,做个hash区分一下不同干员 - ::std::string name_hash; // 预留 - Smiley smiley; - double mood_ratio = 0; // 心情进度条的百分比 - Doing doing = Doing::Invalid; - bool selected = false; // 干员是否已被选择(蓝色的选择框) - ::std::unordered_set skills; - Rect rect; - }; + ::std::string face_hash; // 有些干员的技能是完全一样的,做个hash区分一下不同干员 + ::std::string name_hash; // 预留 + Smiley smiley; + double mood_ratio = 0; // 心情进度条的百分比 + Doing doing = Doing::Invalid; + bool selected = false; // 干员是否已被选择(蓝色的选择框) + ::std::unordered_set skills; + Rect rect; + }; - struct SkillsComb + struct SkillsComb + { + SkillsComb() = default; + SkillsComb(std::unordered_set skill_vec) { - SkillsComb() = default; - SkillsComb(std::unordered_set skill_vec) - { - skills = ::std::move(skill_vec); - for (const auto& s : skills) { - for (const auto& [key, value] : s.efficient) { - efficient[key] += value; - } - for (const auto& [key, reg] : s.efficient_regex) { - efficient_regex[key] += "+(" + reg + ")"; - } + skills = ::std::move(skill_vec); + for (const auto& s : skills) { + for (const auto& [key, value] : s.efficient) { + efficient[key] += value; + } + for (const auto& [key, reg] : s.efficient_regex) { + efficient_regex[key] += "+(" + reg + ")"; } } - bool operator==(const SkillsComb& rhs) const - { - return skills == rhs.skills; - } - - ::std::string desc; - ::std::unordered_set skills; - ::std::unordered_map efficient; - ::std::unordered_map efficient_regex; - - ::std::string name_hash; - bool hash_filter = false; - ::std::unordered_map possible_hashs; // 限定只允许某些hash匹配的某些干员。若hash不相同,即使技能匹配了也不可用。hashs若为空,则不生效 - }; - // 基建技能组 - struct SkillsGroup + } + bool operator==(const SkillsComb& rhs) const { - ::std::string desc; // 文字介绍,实际不起作用 - ::std::unordered_map conditions; // 技能组合可用条件,例如:key 发电站数量,value 3 - ::std::vector necessary; // 必选技能。这里面的缺少任一,则该技能组合不可用 - ::std::vector optional; // 可选技能。 - bool allow_external = false; // 当干员数没满3个的时候,是否允许补充外部干员 - }; + return skills == rhs.skills; + } - enum class WorkMode - { - Invalid = -1, - Gentle, // 温和换班模式:会对干员人数不满的设施进行换班,计算单设施内最优解,尽量不破坏原有的干员组合;即若设施内干员是满的,则不对该设施进行换班 - Aggressive, // 激进换班模式:会对每一个设施进行换班,计算单设施内最优解,但不会将其他设施中的干员替换过来;即按工作状态排序,仅选择前面的干员 - Extreme // 偏激换班模式:会对每一个设施进行换班,计算全局的单设施内最优解,为追求更高效率,会将其他设施内的干员也替换过来;即按技能排序,计算所有拥有该设施技能的干员效率,无论他在不在其他地方工作 - }; - } + ::std::string desc; + ::std::unordered_set skills; + ::std::unordered_map efficient; + ::std::unordered_map efficient_regex; + + ::std::string name_hash; + bool hash_filter = false; + ::std::unordered_map possible_hashs; // 限定只允许某些hash匹配的某些干员。若hash不相同,即使技能匹配了也不可用。hashs若为空,则不生效 + }; + // 基建技能组 + struct SkillsGroup + { + ::std::string desc; // 文字介绍,实际不起作用 + ::std::unordered_map conditions; // 技能组合可用条件,例如:key 发电站数量,value 3 + ::std::vector necessary; // 必选技能。这里面的缺少任一,则该技能组合不可用 + ::std::vector optional; // 可选技能。 + bool allow_external = false; // 当干员数没满3个的时候,是否允许补充外部干员 + }; + + enum class WorkMode + { + Invalid = -1, + Gentle, // 温和换班模式:会对干员人数不满的设施进行换班,计算单设施内最优解,尽量不破坏原有的干员组合;即若设施内干员是满的,则不对该设施进行换班 + Aggressive, // 激进换班模式:会对每一个设施进行换班,计算单设施内最优解,但不会将其他设施中的干员替换过来;即按工作状态排序,仅选择前面的干员 + Extreme // 偏激换班模式:会对每一个设施进行换班,计算全局的单设施内最优解,为追求更高效率,会将其他设施内的干员也替换过来;即按技能排序,计算所有拥有该设施技能的干员效率,无论他在不在其他地方工作 + }; } diff --git a/src/MeoAssistant/AsstUtils.hpp b/src/MeoAssistant/AsstUtils.hpp index e393734d72..165b6fe200 100644 --- a/src/MeoAssistant/AsstUtils.hpp +++ b/src/MeoAssistant/AsstUtils.hpp @@ -7,7 +7,7 @@ #ifdef _WIN32 #include #else -#include +#include #include #include #include @@ -15,305 +15,296 @@ #include #endif -namespace asst +namespace asst::utils { - namespace utils + inline std::string string_replace_all(const std::string& src, const std::string& old_value, const std::string& new_value) { - inline std::string string_replace_all(const std::string& src, const std::string& old_value, const std::string& new_value) - { - std::string str = src; - for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) { - if ((pos = str.find(old_value, pos)) != std::string::npos) - str.replace(pos, old_value.length(), new_value); - else - break; - } - return str; + std::string str = src; + for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) { + if ((pos = str.find(old_value, pos)) != std::string::npos) + str.replace(pos, old_value.length(), new_value); + else + break; } + return str; + } - inline std::string string_replace_all_batch(const std::string& src, const std::vector>& replace_pairs) - { - std::string str = src; - for (auto& [old_value, new_value] : replace_pairs) { - str = string_replace_all(str, old_value, new_value); - } - return str; + inline std::string string_replace_all_batch(const std::string& src, const std::vector>& replace_pairs) + { + std::string str = src; + for (auto& [old_value, new_value] : replace_pairs) { + str = string_replace_all(str, old_value, new_value); } + return str; + } - inline std::vector string_split(const std::string& str, const std::string& delimiter) - { - std::string::size_type pos1 = 0; - std::string::size_type pos2 = str.find(delimiter); - std::vector result; + inline std::vector string_split(const std::string& str, const std::string& delimiter) + { + std::string::size_type pos1 = 0; + std::string::size_type pos2 = str.find(delimiter); + std::vector result; - while (std::string::npos != pos2) { - result.emplace_back(str.substr(pos1, pos2 - pos1)); + while (std::string::npos != pos2) { + result.emplace_back(str.substr(pos1, pos2 - pos1)); - pos1 = pos2 + delimiter.size(); - pos2 = str.find(delimiter, pos1); - } - if (pos1 != str.length()) - result.emplace_back(str.substr(pos1)); - - return result; + pos1 = pos2 + delimiter.size(); + pos2 = str.find(delimiter, pos1); } + if (pos1 != str.length()) + result.emplace_back(str.substr(pos1)); - inline std::string get_format_time() - { - char buff[128] = { 0 }; + return result; + } + + inline std::string get_format_time() + { + char buff[128] = { 0 }; #ifdef _WIN32 - SYSTEMTIME curtime; - GetLocalTime(&curtime); + SYSTEMTIME curtime; + GetLocalTime(&curtime); #ifdef _MSC_VER - sprintf_s(buff, sizeof(buff), + sprintf_s(buff, sizeof(buff), #else // ! _MSC_VER - sprintf(buff, + sprintf(buff, #endif // END _MSC_VER - "%04d-%02d-%02d %02d:%02d:%02d.%03d", - curtime.wYear, curtime.wMonth, curtime.wDay, - curtime.wHour, curtime.wMinute, curtime.wSecond, curtime.wMilliseconds); + "%04d-%02d-%02d %02d:%02d:%02d.%03d", + curtime.wYear, curtime.wMonth, curtime.wDay, + curtime.wHour, curtime.wMinute, curtime.wSecond, curtime.wMilliseconds); #else // ! _WIN32 - struct timeval tv = { 0 }; - gettimeofday(&tv, nullptr); - time_t nowtime = tv.tv_sec; - struct tm* tm_info = localtime(&nowtime); - strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", tm_info); - sprintf(buff, "%s.%03ld", buff, tv.tv_usec / 1000); + struct timeval tv{}; + gettimeofday(&tv, nullptr); + time_t nowtime = tv.tv_sec; + struct tm* tm_info = localtime(&nowtime); + strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", tm_info); + sprintf(buff, "%s.%03ld", buff, tv.tv_usec / 1000); #endif // END _WIN32 - return buff; - } + return buff; + } - inline std::string ansi_to_utf8(const std::string& ansi_str) - { + inline std::string ansi_to_utf8(const std::string& ansi_str) + { #ifdef _WIN32 - const char* src_str = ansi_str.c_str(); - int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, nullptr, 0); - const std::size_t wstr_length = static_cast(len) + 1U; - auto wstr = new wchar_t[wstr_length]; - memset(wstr, 0, sizeof(wstr[0]) * wstr_length); - MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len); + const char* src_str = ansi_str.c_str(); + int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, nullptr, 0); + const std::size_t wstr_length = static_cast(len) + 1U; + auto wstr = new wchar_t[wstr_length]; + memset(wstr, 0, sizeof(wstr[0]) * wstr_length); + MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len); - len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); - const std::size_t str_length = static_cast(len) + 1; - auto str = new char[str_length]; - memset(str, 0, sizeof(str[0]) * str_length); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, nullptr, nullptr); - std::string strTemp = str; + len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); + const std::size_t str_length = static_cast(len) + 1; + auto str = new char[str_length]; + memset(str, 0, sizeof(str[0]) * str_length); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, nullptr, nullptr); + std::string strTemp = str; - if (wstr) { - delete[] wstr; - wstr = nullptr; - } - if (str) { - delete[] str; - str = nullptr; - } - return strTemp; + delete[] wstr; + wstr = nullptr; + delete[] str; + str = nullptr; + + return strTemp; #else // Don't fucking use gbk in linux! - return gbk_str; + return ansi_str; #endif - } + } - inline std::string utf8_to_ansi(const std::string& utf8_str) - { + inline std::string utf8_to_ansi(const std::string& utf8_str) + { #ifdef _WIN32 - const char* src_str = utf8_str.c_str(); - int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, nullptr, 0); - const std::size_t wsz_ansi_length = static_cast(len) + 1; - auto wsz_ansi = new wchar_t[wsz_ansi_length]; - memset(wsz_ansi, 0, sizeof(wsz_ansi[0]) * wsz_ansi_length); - MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wsz_ansi, len); + const char* src_str = utf8_str.c_str(); + int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, nullptr, 0); + const std::size_t wsz_ansi_length = static_cast(len) + 1; + auto wsz_ansi = new wchar_t[wsz_ansi_length]; + memset(wsz_ansi, 0, sizeof(wsz_ansi[0]) * wsz_ansi_length); + MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wsz_ansi, len); - len = WideCharToMultiByte(CP_ACP, 0, wsz_ansi, -1, nullptr, 0, nullptr, nullptr); - const std::size_t sz_ansi_length = static_cast(len) + 1; - auto sz_ansi = new char[sz_ansi_length]; - memset(sz_ansi, 0, sizeof(sz_ansi[0]) * sz_ansi_length); - WideCharToMultiByte(CP_ACP, 0, wsz_ansi, -1, sz_ansi, len, nullptr, nullptr); - std::string strTemp(sz_ansi); + len = WideCharToMultiByte(CP_ACP, 0, wsz_ansi, -1, nullptr, 0, nullptr, nullptr); + const std::size_t sz_ansi_length = static_cast(len) + 1; + auto sz_ansi = new char[sz_ansi_length]; + memset(sz_ansi, 0, sizeof(sz_ansi[0]) * sz_ansi_length); + WideCharToMultiByte(CP_ACP, 0, wsz_ansi, -1, sz_ansi, len, nullptr, nullptr); + std::string strTemp(sz_ansi); - if (wsz_ansi) { - delete[] wsz_ansi; - wsz_ansi = nullptr; - } - if (sz_ansi) { - delete[] sz_ansi; - sz_ansi = nullptr; - } - return strTemp; + delete[] wsz_ansi; + wsz_ansi = nullptr; + delete[] sz_ansi; + sz_ansi = nullptr; + + return strTemp; #else // Don't fucking use gbk in linux! - return utf8_str; + return utf8_str; #endif + } + + template + constexpr inline RetTy make_rect(const ArgType& rect) + { + return RetTy{ rect.x, rect.y, rect.width, rect.height }; + } + + inline std::string load_file_without_bom(const std::string& filename) + { + std::ifstream ifs(filename, std::ios::in); + if (!ifs.is_open()) { + return {}; } + std::stringstream iss; + iss << ifs.rdbuf(); + ifs.close(); + std::string str = iss.str(); - template - constexpr inline RetTy make_rect(const ArgType& rect) - { - return RetTy{ rect.x, rect.y, rect.width, rect.height }; - } + using uchar = unsigned char; + constexpr static uchar Bom_0 = 0xEF; + constexpr static uchar Bom_1 = 0xBB; + constexpr static uchar Bom_2 = 0xBF; - inline std::string load_file_without_bom(const std::string& filename) - { - std::ifstream ifs(filename, std::ios::in); - if (!ifs.is_open()) { - return {}; - } - std::stringstream iss; - iss << ifs.rdbuf(); - ifs.close(); - std::string str = iss.str(); - - using uchar = unsigned char; - constexpr static uchar Bom_0 = 0xEF; - constexpr static uchar Bom_1 = 0xBB; - constexpr static uchar Bom_2 = 0xBF; - - if (str.size() >= 3 && static_cast(str.at(0)) == Bom_0 && static_cast(str.at(1)) == Bom_1 && static_cast(str.at(2)) == Bom_2) { - str.assign(str.begin() + 3, str.end()); - return str; - } + if (str.size() >= 3 && static_cast(str.at(0)) == Bom_0 && static_cast(str.at(1)) == Bom_1 && static_cast(str.at(2)) == Bom_2) { + str.assign(str.begin() + 3, str.end()); return str; } + return str; + } - inline std::string callcmd(const std::string& cmdline) - { - constexpr int PipeBuffSize = 4096; - std::string pipe_str; - auto pipe_buffer = std::make_unique(PipeBuffSize); + inline std::string callcmd(const std::string& cmdline) + { + constexpr int PipeBuffSize = 4096; + std::string pipe_str; + auto pipe_buffer = std::make_unique(PipeBuffSize); #ifdef _WIN32 - SECURITY_ATTRIBUTES pipe_sec_attr = { 0 }; - pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - pipe_sec_attr.lpSecurityDescriptor = nullptr; - pipe_sec_attr.bInheritHandle = TRUE; - HANDLE pipe_read = nullptr; - HANDLE pipe_child_write = nullptr; - CreatePipe(&pipe_read, &pipe_child_write, &pipe_sec_attr, PipeBuffSize); + SECURITY_ATTRIBUTES pipe_sec_attr = { 0 }; + pipe_sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + pipe_sec_attr.lpSecurityDescriptor = nullptr; + pipe_sec_attr.bInheritHandle = TRUE; + HANDLE pipe_read = nullptr; + HANDLE pipe_child_write = nullptr; + CreatePipe(&pipe_read, &pipe_child_write, &pipe_sec_attr, PipeBuffSize); - STARTUPINFOA si = { 0 }; - si.cb = sizeof(STARTUPINFO); - si.dwFlags = STARTF_USESTDHANDLES; - si.wShowWindow = SW_HIDE; - si.hStdOutput = pipe_child_write; - si.hStdError = pipe_child_write; + STARTUPINFOA si = { 0 }; + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdOutput = pipe_child_write; + si.hStdError = pipe_child_write; - PROCESS_INFORMATION pi = { nullptr }; + PROCESS_INFORMATION pi = { nullptr }; - BOOL p_ret = CreateProcessA(nullptr, const_cast(cmdline.c_str()), nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi); - if (p_ret) { - DWORD peek_num = 0; - DWORD read_num = 0; - do { - while (::PeekNamedPipe(pipe_read, nullptr, 0, nullptr, &peek_num, nullptr) && peek_num > 0) { - if (::ReadFile(pipe_read, pipe_buffer.get(), peek_num, &read_num, nullptr)) { - pipe_str.append(pipe_buffer.get(), pipe_buffer.get() + read_num); - } - } - } while (::WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT); - - DWORD exit_ret = 255; - ::GetExitCodeProcess(pi.hProcess, &exit_ret); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - - ::CloseHandle(pipe_read); - ::CloseHandle(pipe_child_write); - -#else - constexpr static int PIPE_READ = 0; - constexpr static int PIPE_WRITE = 1; - int pipe_in[2] = { 0 }; - int pipe_out[2] = { 0 }; - int pipe_in_ret = pipe(pipe_in); - int pipe_out_ret = pipe(pipe_out); - fcntl(pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); - int exit_ret = 0; - int child = fork(); - if (child == 0) { - // child process - dup2(pipe_in[PIPE_READ], STDIN_FILENO); - dup2(pipe_out[PIPE_WRITE], STDOUT_FILENO); - dup2(pipe_out[PIPE_WRITE], STDERR_FILENO); - - // all these are for use by parent only - close(pipe_in[PIPE_READ]); - close(pipe_in[PIPE_WRITE]); - close(pipe_out[PIPE_READ]); - close(pipe_out[PIPE_WRITE]); - - exit_ret = execlp("sh", "sh", "-c", cmdline.c_str(), nullptr); - exit(exit_ret); - } - else if (child > 0) { - // parent process - - // close unused file descriptors, these are for child only - close(pipe_in[PIPE_READ]); - close(pipe_out[PIPE_WRITE]); - - do { - ssize_t read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), PipeBuffSize); - - while (read_num > 0) { + BOOL p_ret = CreateProcessA(nullptr, const_cast(cmdline.c_str()), nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi); + if (p_ret) { + DWORD peek_num = 0; + DWORD read_num = 0; + do { + while (::PeekNamedPipe(pipe_read, nullptr, 0, nullptr, &peek_num, nullptr) && peek_num > 0) { + if (::ReadFile(pipe_read, pipe_buffer.get(), peek_num, &read_num, nullptr)) { pipe_str.append(pipe_buffer.get(), pipe_buffer.get() + read_num); - read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), PipeBuffSize); - }; - } while (::waitpid(child, &exit_ret, WNOHANG) == 0); + } + } + } while (::WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT); - close(pipe_in[PIPE_WRITE]); - close(pipe_out[PIPE_READ]); - } - else { - // failed to create child process - close(pipe_in[PIPE_READ]); - close(pipe_in[PIPE_WRITE]); - close(pipe_out[PIPE_READ]); - close(pipe_out[PIPE_WRITE]); - } -#endif - return pipe_str; + DWORD exit_ret = 255; + ::GetExitCodeProcess(pi.hProcess, &exit_ret); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); } - // template::value>::type> - // std::string VectorToString(const std::vector& vector, bool to_gbk = false) { - // if (vector.empty()) { - // return std::string(); - // } - // static const std::string inter = ", "; - // std::string str; - // for (const T& ele : vector) { - // if (to_gbk) { - // str += utils::utf8_to_ansi(ele) + inter; - // } - // else { - // str += ele + inter; - // } - // } + ::CloseHandle(pipe_read); + ::CloseHandle(pipe_child_write); - // if (!str.empty()) { - // str.erase(str.size() - inter.size(), inter.size()); - // } - // return str; - //} +#else + constexpr static int PIPE_READ = 0; + constexpr static int PIPE_WRITE = 1; + int pipe_in[2] = { 0 }; + int pipe_out[2] = { 0 }; + int pipe_in_ret = pipe(pipe_in); + int pipe_out_ret = pipe(pipe_out); + fcntl(pipe_out[PIPE_READ], F_SETFL, O_NONBLOCK); + int exit_ret = 0; + int child = fork(); + if (child == 0) { + // child process + dup2(pipe_in[PIPE_READ], STDIN_FILENO); + dup2(pipe_out[PIPE_WRITE], STDOUT_FILENO); + dup2(pipe_out[PIPE_WRITE], STDERR_FILENO); - //template::value>::type> - // std::string VectorToString(const std::vector& vector) { - // if (vector.empty()) { - // return std::string(); - // } - // static const std::string inter = ", "; - // std::string str; - // for (const T& ele : vector) { - // str += std::to_string(ele) + inter; - // } + // all these are for use by parent only + close(pipe_in[PIPE_READ]); + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); - // if (!str.empty()) { - // str.erase(str.size() - inter.size(), inter.size()); - // } - // return str; - //} + exit_ret = execlp("sh", "sh", "-c", cmdline.c_str(), nullptr); + exit(exit_ret); + } + else if (child > 0) { + // parent process + + // close unused file descriptors, these are for child only + close(pipe_in[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); + + do { + ssize_t read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), PipeBuffSize); + + while (read_num > 0) { + pipe_str.append(pipe_buffer.get(), pipe_buffer.get() + read_num); + read_num = read(pipe_out[PIPE_READ], pipe_buffer.get(), PipeBuffSize); + }; + } while (::waitpid(child, &exit_ret, WNOHANG) == 0); + + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); + } + else { + // failed to create child process + close(pipe_in[PIPE_READ]); + close(pipe_in[PIPE_WRITE]); + close(pipe_out[PIPE_READ]); + close(pipe_out[PIPE_WRITE]); + } +#endif + return pipe_str; } + + // template::value>::type> + // std::string VectorToString(const std::vector& vector, bool to_gbk = false) { + // if (vector.empty()) { + // return std::string(); + // } + // static const std::string inter = ", "; + // std::string str; + // for (const T& ele : vector) { + // if (to_gbk) { + // str += utils::utf8_to_ansi(ele) + inter; + // } + // else { + // str += ele + inter; + // } + // } + + // if (!str.empty()) { + // str.erase(str.size() - inter.size(), inter.size()); + // } + // return str; + //} + + //template::value>::type> + // std::string VectorToString(const std::vector& vector) { + // if (vector.empty()) { + // return std::string(); + // } + // static const std::string inter = ", "; + // std::string str; + // for (const T& ele : vector) { + // str += std::to_string(ele) + inter; + // } + + // if (!str.empty()) { + // str.erase(str.size() - inter.size(), inter.size()); + // } + // return str; + //} } diff --git a/src/MeoAssistant/AutoRecruitTask.cpp b/src/MeoAssistant/AutoRecruitTask.cpp index 5c2833632e..bfd75a6ac2 100644 --- a/src/MeoAssistant/AutoRecruitTask.cpp +++ b/src/MeoAssistant/AutoRecruitTask.cpp @@ -121,45 +121,56 @@ bool asst::AutoRecruitTask::recruit_index(size_t index) bool asst::AutoRecruitTask::calc_and_recruit() { LogTraceFunction; - RecruitCalcTask recruit_task(m_callback, m_callback_arg, m_task_chain); - recruit_task.set_param(m_select_level, true, m_skip_robot) - .set_retry_times(m_retry_times) - .set_exit_flag(m_exit_flag) - .set_ctrler(m_ctrler) - .set_status(m_status) - .set_task_id(m_task_id); - // 识别错误,放弃这个公招位,直接返回 - if (!recruit_task.run()) { - callback(AsstMsg::SubTaskError, basic_info()); - click_return_button(); - return true; - } + int maybe_level; + bool has_robot_tag; - int maybe_level = recruit_task.get_maybe_level(); - if (need_exit()) { - return false; - } - // 尝试刷新 - if (m_need_refresh && maybe_level == 3 - && !recruit_task.get_has_special_tag() - && recruit_task.get_has_refresh() - && !(m_skip_robot && recruit_task.get_has_robot_tag())) { - if (refresh()) { - return calc_and_recruit(); + while (true) { + RecruitCalcTask recruit_task(m_callback, m_callback_arg, m_task_chain); + recruit_task.set_param(m_select_level, true, m_skip_robot) + .set_retry_times(m_retry_times) + .set_exit_flag(m_exit_flag) + .set_ctrler(m_ctrler) + .set_status(m_status) + .set_task_id(m_task_id); + + // 识别错误,放弃这个公招位,直接返回 + if (!recruit_task.run()) { + callback(AsstMsg::SubTaskError, basic_info()); + click_return_button(); + return true; } - } - // 如果时间没调整过,那 tag 十有八九也没选,重新试一次 - // 造成时间没调的原因可见: https://github.com/MaaAssistantArknights/MaaAssistantArknights/pull/300#issuecomment-1073287984 - if (check_time_unreduced()) { - return calc_and_recruit(); + + has_robot_tag = recruit_task.get_has_robot_tag(); + maybe_level = recruit_task.get_maybe_level(); + if (need_exit()) { + return false; + } + // 尝试刷新 + if (m_need_refresh && maybe_level == 3 + && !recruit_task.get_has_special_tag() + && recruit_task.get_has_refresh() + && !(m_skip_robot && has_robot_tag)) { + if (refresh()) { + Log.trace("recruit tags refreshed, rerunning recruit task"); + continue; + } + } + // 如果时间没调整过,那 tag 十有八九也没选,重新试一次 + // 造成时间没调的原因可见: https://github.com/MaaAssistantArknights/MaaAssistantArknights/pull/300#issuecomment-1073287984 + if (check_time_unreduced()) { + Log.trace("unreduced recruit check time detected, rerunning recruit task"); + continue; + } + + break; } if (need_exit()) { return false; } - if (!(m_skip_robot && recruit_task.get_has_robot_tag()) && std::find(m_confirm_level.cbegin(), m_confirm_level.cend(), maybe_level) != m_confirm_level.cend()) { + if (!(m_skip_robot && has_robot_tag) && std::find(m_confirm_level.cbegin(), m_confirm_level.cend(), maybe_level) != m_confirm_level.cend()) { if (!confirm()) { return false; } diff --git a/src/MeoAssistant/BattleProcessTask.cpp b/src/MeoAssistant/BattleProcessTask.cpp index 846dab9ea8..b3f3a9ca8b 100644 --- a/src/MeoAssistant/BattleProcessTask.cpp +++ b/src/MeoAssistant/BattleProcessTask.cpp @@ -25,6 +25,10 @@ bool asst::BattleProcessTask::_run() bool ret = get_stage_info(); if (!ret) { + json::value info = basic_info_with_what("UnsupportedLevel"); + auto& details = info["details"]; + details["level"] = m_stage_name; + callback(AsstMsg::SubTaskExtraInfo, info); return false; } @@ -133,12 +137,16 @@ bool asst::BattleProcessTask::analyze_opers_preview() } auto draw_future = std::async(std::launch::async, [&]() { + std::filesystem::create_directory("map"); for (const auto& [loc, info] : m_normal_tile_info) { std::string text = "( " + std::to_string(loc.x) + ", " + std::to_string(loc.y) + " )"; cv::putText(draw, text, cv::Point(info.pos.x - 30, info.pos.y), 1, 1.2, cv::Scalar(0, 0, 255), 2); } - - cv::imwrite("map.png", draw); +#ifdef WIN32 + cv::imwrite("map/" + utils::utf8_to_ansi(m_stage_name) + ".png", draw); +#else + cv::imwrite("map/" + m_stage_name + ".png", draw); +#endif }); auto opers = oper_analyzer.get_opers(); @@ -474,12 +482,13 @@ bool asst::BattleProcessTask::oper_deploy(const BattleAction& action) // 拖动到场上 Point placed_point = m_side_tile_info[action.location].pos; - Rect placed_rect{ placed_point.x ,placed_point.y, 1, 1 }; + Rect placed_rect{ placed_point.x ,placed_point.y, 0, 0 }; int dist = static_cast( std::sqrt( - (std::abs(placed_point.x - oper_rect.x) << 1) - + (std::abs(placed_point.y - oper_rect.y) << 1))); - int duration = static_cast(swipe_oper_task_ptr->pre_delay / 1000 * dist); // 随便取的一个系数 + (std::pow(std::abs(placed_point.x - oper_rect.x), 2)) + + (std::pow(std::abs(placed_point.y - oper_rect.y), 2)))); + // 1000 是随便取的一个系数,把整数的 pre_delay 转成小数用的 + int duration = static_cast(swipe_oper_task_ptr->pre_delay / 1000.0 * dist * log10(dist)); m_ctrler->swipe(oper_rect, placed_rect, duration, true, 0); sleep(use_oper_task_ptr->rear_delay); @@ -633,6 +642,253 @@ void asst::BattleProcessTask::sleep_with_possible_skill(unsigned millisecond) Log.trace("end of sleep_with_possible_skill", millisecond); } +template +std::optional> +asst::BattleProcessTask::get_char_allocation_for_each_group( + const std::unordered_map>& group_list, + const std::vector& char_list) +{ + /* + * * dlx 算法简介 + * + * https://oi-wiki.org/search/dlx/ + * + * + * * dlx 算法作用 + * + * 在形如: + * a: 10010 + * b: 01110 + * c: 01001 + * d: 00100 + * e: 11010 + * 这样的数据里, + * dlx 可以找到 {a, c, d} 这样每列恰好出现且仅出现一次 1 的数据, + * 也即对全集的一个精确覆盖: + * a: 10010 + * c: 01001 + * d: 00100 + * 11111 + * + * + * * dlx 算法建模 + * + * dlx 的列分为 [组号] [干员号] 两部分 + * dlx 的行分为 [可能的选择对] [不选择该干员] 两部分 + * + * [可能的选择对]: + * 每行对应一种可能的选择, + * 将组号,干员号对应位置的列设为1 + * + * [不选择该干员]: + * 每行对应不选择某干员的情况, + * 将干员号对应位置的列设为1 + * + * + * * dlx 建模示例 + * + * 有以下分组: + * a: {1, 3, 4} + * b: {2, 3, 5} + * c: {1, 2, 3} + * 拥有的干员: + * {1, 2, 4, 5, 6} + * + * 先处理出所有可能的情况: + * a: {1, 4} + * b: {2, 5} + * c: {1, 2} + * + * 构造表: + * abc 1245 + * 1 100 1000 + * 2 100 0010 + * 3 010 0100 + * 4 010 0001 + * 5 001 1000 + * 6 001 0100 + * 7 000 1000 ~1 + * 9 000 0100 ~2 + * 9 000 0010 ~4 + * A 000 0001 ~5 + * + * 使用dlx求得一组解: + * 一个可能的结果是: + * 行号 {2, 3, 5, A} + * 即 {, , , ~5} + * + * 输出分组结果: + * a: 4 + * b: 2 + * c: 1 + * + */ + + // dlx 算法模板类 + class DancingLinksModel + { + private: + + size_t index{}; + std::vector first, size; + std::vector left, right, up, down; + std::vector column, row; + + void remove(const size_t &column_id) { + left[right[column_id]] = left[column_id]; + right[left[column_id]] = right[column_id]; + for (size_t i = down[column_id]; i != column_id; i = down[i]) { + for (size_t j = right[i]; j != i; j = right[j]) { + up[down[j]] = up[j]; + down[up[j]] = down[j]; + --size[column[j]]; + } + } + } + + void recover(const size_t &column_id) { + for (size_t i = up[column_id]; i != column_id; i = up[i]) { + for (size_t j = left[i]; j != i; j = left[j]) { + up[down[j]] = down[up[j]] = j; + ++size[column[j]]; + } + } + left[right[column_id]] = right[left[column_id]] = column_id; + } + + public: + + size_t answer_stack_size{}; + std::vector answer_stack; + + DancingLinksModel(const size_t &max_node_num, const size_t &max_ans_size) : + first(max_node_num), + size(max_node_num), + left(max_node_num), + right(max_node_num), + up(max_node_num), + down(max_node_num), + column(max_node_num), + row(max_node_num), + answer_stack(max_ans_size) { + } + + void build(const size_t &column_id) { + for (size_t i = 0; i <= column_id; i++) { + left[i] = i - 1; + right[i] = i + 1; + up[i] = down[i] = i; + } + left[0] = column_id; + right[column_id] = 0; + index = column_id; + first.clear(); + size.clear(); + } + + void insert(const size_t &row_id, const size_t &column_id) { + column[++index] = column_id; + row[index] = row_id; + ++size[column_id]; + down[index] = down[column_id]; + up[down[column_id]] = index; + up[index] = column_id; + down[column_id] = index; + if (!first[row_id]) { + first[row_id] = left[index] = right[index] = index; + } else { + right[index] = right[first[row_id]]; + left[right[first[row_id]]] = index; + left[index] = first[row_id]; + right[first[row_id]] = index; + } + } + + bool dance(const size_t &depth) { + if (!right[0]) { + answer_stack_size = depth; + return true; + } + size_t column_id = right[0]; + for (size_t i = right[0]; i != 0; i = right[i]) { + if (size[i] < size[column_id]) { + column_id = i; + } + } + remove(column_id); + for (size_t i = down[column_id]; i != column_id; i = down[i]) { + answer_stack[depth] = row[i]; + for (size_t j = right[i]; j != i; j = right[j]) { + remove(column[j]); + } + if (dance(depth + 1)) { + return true; + } + for (size_t j = left[i]; j != i; j = left[j]) { + recover(column[j]); + } + } + recover(column_id); + return false; + } + }; + + // 建立结点、组、干员与各自 id 的映射关系 + std::vector> node_id_mapping; + std::vector group_id_mapping; + std::vector char_id_mapping; + std::unordered_map group_name_mapping; + std::unordered_map char_name_mapping; + std::set char_set(char_list.begin(), char_list.end()); + + for (auto &i: group_list) { + group_name_mapping[i.first] = group_id_mapping.size(); + group_id_mapping.emplace_back(i.first); + for (auto &j: i.second) { + if (char_set.contains(j)) { + node_id_mapping.emplace_back(i.first, j); + if (!char_name_mapping.contains(j)) { + char_name_mapping[j] = char_id_mapping.size(); + char_id_mapping.emplace_back(j); + } + } + } + } + + // 建 01 矩阵 + const size_t node_num = node_id_mapping.size(); + const size_t group_num = group_id_mapping.size(); + const size_t char_num = char_id_mapping.size(); + + DancingLinksModel dancing_links_model(2 * node_num + group_num + 2 * char_num + 1, group_num + char_num); + + dancing_links_model.build(group_num + char_num); + + for (size_t i = 0; i < node_num; i++) { + dancing_links_model.insert(i + 1, group_name_mapping[node_id_mapping[i].first] + 1); + dancing_links_model.insert(i + 1, group_num + char_name_mapping[node_id_mapping[i].second] + 1); + } + + for (size_t i = 0; i < char_num; i++) { + dancing_links_model.insert(i + node_num + 1, i + group_num + 1); + } + + // dance!! + bool has_solution = dancing_links_model.dance(0); + + // 判定结果 + if (!has_solution) return std::nullopt; + + std::unordered_map return_value; + + for (size_t i = 0; i < dancing_links_model.answer_stack_size; i++) { + if (dancing_links_model.answer_stack[i] > node_num) break; + return_value.insert(node_id_mapping[dancing_links_model.answer_stack[i] - 1]); + } + + return return_value; +} + bool asst::BattleProcessTask::battle_pause() { return ProcessTask(*this, { "BattlePause" }).run(); diff --git a/src/MeoAssistant/BattleProcessTask.h b/src/MeoAssistant/BattleProcessTask.h index 4b4ea54615..dd07fa9f9d 100644 --- a/src/MeoAssistant/BattleProcessTask.h +++ b/src/MeoAssistant/BattleProcessTask.h @@ -36,6 +36,11 @@ namespace asst bool try_possible_skill(const cv::Mat& image); void sleep_with_possible_skill(unsigned millisecond); + template + static std::optional> get_char_allocation_for_each_group( + const std::unordered_map>& group_list, + const std::vector& char_list); + std::string m_stage_name; std::unordered_map m_side_tile_info; diff --git a/src/MeoAssistant/Controller.cpp b/src/MeoAssistant/Controller.cpp index 71ee662a1f..893e45c643 100644 --- a/src/MeoAssistant/Controller.cpp +++ b/src/MeoAssistant/Controller.cpp @@ -57,7 +57,7 @@ private: asst::Controller::Controller(AsstCallback callback, void* callback_arg) : m_callback(std::move(callback)), m_callback_arg(callback_arg), - m_rand_engine(static_cast(time(nullptr))) + m_rand_engine(std::random_device{}()) { LogTraceFunction; @@ -242,7 +242,7 @@ void asst::Controller::pipe_working_proc() } } -std::optional> asst::Controller::call_command(const std::string& cmd, int64_t timeout, bool recv_by_socket) +std::optional> asst::Controller::call_command(const std::string& cmd, int64_t timeout, bool recv_by_socket) { LogTraceScope(std::string(__FUNCTION__) + " | `" + cmd + "`"); @@ -281,12 +281,12 @@ std::optional> asst::Controller::call_command(const s std::unique_lock pipe_lock(m_pipe_mutex); fd_set fdset = { 0 }; FD_SET(m_server_sock, &fdset); - constexpr int TimeoutMilliseconds = 10000; - timeval select_timeout = { TimeoutMilliseconds / 1000, (TimeoutMilliseconds % 1000) * 1000}; + constexpr int TimeoutMilliseconds = 5000; + timeval select_timeout = { TimeoutMilliseconds / 1000, (TimeoutMilliseconds % 1000) * 1000 }; select(static_cast(m_server_sock) + 1, &fdset, NULL, NULL, &select_timeout); if (FD_ISSET(m_server_sock, &fdset)) { SOCKET client_sock = ::accept(m_server_sock, NULL, NULL); - setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&TimeoutMilliseconds, sizeof(int)); + setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&TimeoutMilliseconds, sizeof(int)); int recv_size = 0; do { recv_size = ::recv(client_sock, (char*)m_socket_buffer.get(), SocketBuffSize, NULL); @@ -356,6 +356,7 @@ std::optional> asst::Controller::call_command(const s else if (m_inited) { // 之前可以运行,突然运行不了了,这种情况多半是 adb 炸了。所以重新连接一下 m_inited = false; + std::this_thread::sleep_for(std::chrono::seconds(10)); auto reconnect_ret = call_command(m_adb.connect, 60 * 1000); bool is_reconnect_success = false; if (reconnect_ret) { @@ -380,6 +381,7 @@ std::optional> asst::Controller::call_command(const s { "cmd", cmd }, { "recv_by_socket", recv_by_socket} }} }; + m_inited = false; m_callback(AsstMsg::ConnectionInfo, info, m_callback_arg); } } @@ -391,26 +393,26 @@ std::optional> asst::Controller::call_command(const s { "details", json::object { { "cmd", m_adb.connect } }} }; + m_inited = false; m_callback(AsstMsg::ConnectionInfo, info, m_callback_arg); } } return std::nullopt; } -void asst::Controller::convert_lf(std::vector& data) +void asst::Controller::convert_lf(std::vector& data) { LogTraceFunction; if (data.empty() || data.size() < 2) { return; } - using Iter = std::vector::iterator; - auto pred = [](const Iter& cur) -> bool { + auto pred = [](const std::vector::iterator& cur) -> bool { return *cur == '\r' && *(cur + 1) == '\n'; }; // find the first of "\r\n" - Iter first_iter = data.end(); - for (Iter iter = data.begin(); iter != data.end() - 1; ++iter) { + auto first_iter = data.end(); + for (auto iter = data.begin(); iter != data.end() - 1; ++iter) { if (pred(iter)) { first_iter = iter; break; @@ -420,8 +422,8 @@ void asst::Controller::convert_lf(std::vector& data) return; } // move forward all non-crlf elements - Iter end_r1_iter = data.end() - 1; - Iter next_iter = first_iter; + auto end_r1_iter = data.end() - 1; + auto next_iter = first_iter; while (++first_iter != end_r1_iter) { if (!pred(first_iter)) { *next_iter = *first_iter; @@ -461,7 +463,7 @@ void asst::Controller::random_delay() const auto& opt = Resrc.cfg().get_options(); if (opt.control_delay_upper != 0) { LogTraceFunction; - static std::default_random_engine rand_engine(static_cast(time(nullptr))); + static std::default_random_engine rand_engine(std::random_device{}()); static std::uniform_int_distribution rand_uni( opt.control_delay_lower, opt.control_delay_upper); @@ -497,7 +499,7 @@ int asst::Controller::push_cmd(const std::string& cmd) std::unique_lock lock(m_cmd_queue_mutex); m_cmd_queue.emplace(cmd); m_cmd_condvar.notify_one(); - return ++m_push_id; + return int(++m_push_id); } std::optional asst::Controller::try_to_init_socket(const std::string& local_address, unsigned short try_port, unsigned short try_times) @@ -558,6 +560,7 @@ bool asst::Controller::screencap() LogTraceFunction; //if (true) { + // m_inited = true; // std::unique_lock image_lock(m_image_mutex); // m_cache_image = cv::imread("err/1.png"); // return true; @@ -567,7 +570,7 @@ bool asst::Controller::screencap() if (data.empty()) { return false; } - size_t std_size = m_width * m_height * 4; + size_t std_size = 4ULL * m_width * m_height; if (data.size() < std_size) { return false; } @@ -594,7 +597,7 @@ bool asst::Controller::screencap() if (unzip_data.empty()) { return false; } - size_t std_size = m_width * m_height * 4; + size_t std_size = 4ULL * m_width * m_height; if (unzip_data.size() < std_size) { return false; } @@ -629,29 +632,59 @@ bool asst::Controller::screencap() switch (m_adb.screencap_method) { case AdbProperty::ScreencapMethod::UnknownYet: { + Log.info("Try to find the fastest way to screencap"); + m_inited = false; + auto min_cost = std::chrono::nanoseconds(LLONG_MAX); + + auto start_time = std::chrono::high_resolution_clock::now(); if (m_support_socket && m_server_started && screencap(m_adb.screencap_raw_by_nc, decode_raw, true)) { - m_adb.screencap_method = AdbProperty::ScreencapMethod::RawByNc; - Log.info("screencap by RawByNc"); - m_inited = true; - return true; - } - else if (screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip)) { - m_adb.screencap_method = AdbProperty::ScreencapMethod::RawWithGzip; - Log.info("screencap by RawWithGzip"); - m_inited = true; - return true; - } - else if (screencap(m_adb.screencap_encode, decode_encode)) { - m_adb.screencap_method = AdbProperty::ScreencapMethod::Encode; - Log.info("screencap by Encode"); - m_inited = true; - return true; + auto duration = std::chrono::high_resolution_clock::now() - start_time; + if (duration < min_cost) { + m_adb.screencap_method = AdbProperty::ScreencapMethod::RawByNc; + m_inited = true; + min_cost = duration; + } + Log.info("RawByNc cost", duration.count(), "ns"); } else { - return false; + Log.info("RawByNc is not supported"); } + clear_lf_info(); + + start_time = std::chrono::high_resolution_clock::now(); + if (screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip)) { + auto duration = std::chrono::high_resolution_clock::now() - start_time; + if (duration < min_cost) { + m_adb.screencap_method = AdbProperty::ScreencapMethod::RawWithGzip; + m_inited = true; + min_cost = duration; + } + Log.info("RawWithGzip cost", duration.count(), "ns"); + } + else { + Log.info("RawWithGzip is not supported"); + } + clear_lf_info(); + + start_time = std::chrono::high_resolution_clock::now(); + if (screencap(m_adb.screencap_encode, decode_encode)) { + auto duration = std::chrono::high_resolution_clock::now() - start_time; + if (duration < min_cost) { + m_adb.screencap_method = AdbProperty::ScreencapMethod::Encode; + m_inited = true; + min_cost = duration; + } + Log.info("Encode cost", duration.count(), "ns"); + } + else { + Log.info("Encode is not supported"); + } + Log.info("The fastest way is", static_cast(m_adb.screencap_method), ", cost:", min_cost.count(), "ns"); + clear_lf_info(); + return m_inited; } + break; case AdbProperty::ScreencapMethod::RawByNc: { return screencap(m_adb.screencap_raw_by_nc, decode_raw, true); @@ -717,6 +750,11 @@ bool asst::Controller::screencap(const std::string& cmd, const DecodeFunc& decod } } +void asst::Controller::clear_lf_info() +{ + m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::UnknownYet; +} + cv::Mat asst::Controller::get_resized_image() const { const static cv::Size dsize(m_scale_size.first, m_scale_size.second); @@ -863,6 +901,7 @@ bool asst::Controller::connect(const std::string& adb_path, const std::string& a #ifdef ASST_DEBUG if (config == "DEBUG") { + m_inited = true; return true; } #endif @@ -1109,6 +1148,9 @@ bool asst::Controller::connect(const std::string& adb_path, const std::string& a } } + // try to find the fastest way + screencap(); + ++m_instance_count; return true; } @@ -1125,14 +1167,17 @@ bool asst::Controller::release() #ifndef _WIN32 if (m_child) -#else - if (true) #endif { return call_command(m_adb.release).has_value(); } } +bool asst::Controller::inited() const noexcept +{ + return m_inited; +} + const std::string& asst::Controller::get_uuid() const { return m_uuid; @@ -1148,6 +1193,8 @@ cv::Mat asst::Controller::get_image(bool raw) } // 截图之前正常,截图之后不正常了,说明截图过程中发现 adb 炸了 if (inited && !m_inited) { + const static cv::Size dsize(m_scale_size.first, m_scale_size.second); + m_cache_image = cv::Mat(dsize, CV_8UC3); break; } } diff --git a/src/MeoAssistant/Controller.h b/src/MeoAssistant/Controller.h index adbac00e5c..cbb044fdf7 100644 --- a/src/MeoAssistant/Controller.h +++ b/src/MeoAssistant/Controller.h @@ -32,6 +32,7 @@ namespace asst bool connect(const std::string& adb_path, const std::string& address, const std::string& config); bool release(); + bool inited() const noexcept; const std::string& get_uuid() const; cv::Mat get_image(bool raw = false); @@ -63,7 +64,7 @@ namespace asst private: void pipe_working_proc(); - std::optional> call_command(const std::string& cmd, int64_t timeout = 20000, bool recv_by_socket = false); + std::optional> call_command(const std::string& cmd, int64_t timeout = 20000, bool recv_by_socket = false); int push_cmd(const std::string& cmd); std::optional try_to_init_socket(const std::string& local_address, unsigned short try_port, unsigned short try_times = 10U); @@ -71,6 +72,7 @@ namespace asst using DecodeFunc = std::function&)>; bool screencap(); bool screencap(const std::string& cmd, const DecodeFunc& decode_func, bool by_nc = false); + void clear_lf_info(); cv::Mat get_resized_image() const; Point rand_point_in_rect(const Rect& rect); @@ -79,7 +81,7 @@ namespace asst void clear_info() noexcept; // 转换data中所有的crlf为lf:有些模拟器自带的adb,exec-out输出的\n,会被替换成\r\n,导致解码错误,所以这里转一下回来(点名批评mumu) - static void convert_lf(std::vector& data); + static void convert_lf(std::vector& data); AsstCallback m_callback; void* m_callback_arg = nullptr; @@ -137,7 +139,7 @@ namespace asst enum class ScreencapMethod { UnknownYet, - Default, + //Default, RawByNc, RawWithGzip, Encode diff --git a/src/MeoAssistant/CopilotTask.cpp b/src/MeoAssistant/CopilotTask.cpp index 71c6354ef7..8e317f2f3d 100644 --- a/src/MeoAssistant/CopilotTask.cpp +++ b/src/MeoAssistant/CopilotTask.cpp @@ -16,14 +16,14 @@ asst::CopilotTask::CopilotTask(const AsstCallback& callback, void* callback_arg) .set_ignore_error(true); m_subtasks.emplace_back(start_1_tp); - m_subtasks.emplace_back(m_formation_task_ptr)->set_ignore_error(false); + m_subtasks.emplace_back(m_formation_task_ptr)->set_ignore_error(false).set_retry_times(0); auto start_2_tp = std::make_shared(callback, callback_arg, TaskType); start_2_tp->set_tasks({ "BattleStartNormal", "BattleStartRaid", "BattleStartExercise", "BattleStartSimulation" }) .set_ignore_error(false); m_subtasks.emplace_back(start_2_tp); - m_subtasks.emplace_back(m_battle_task_ptr); + m_subtasks.emplace_back(m_battle_task_ptr)->set_retry_times(0); } bool asst::CopilotTask::set_params(const json::value& params) diff --git a/src/MeoAssistant/GeneralConfiger.cpp b/src/MeoAssistant/GeneralConfiger.cpp index 3c29143dfd..3b1f840d27 100644 --- a/src/MeoAssistant/GeneralConfiger.cpp +++ b/src/MeoAssistant/GeneralConfiger.cpp @@ -18,14 +18,6 @@ bool asst::GeneralConfiger::parse(const json::value& json) m_options.adb_extra_swipe_dist = options_json.get("adbExtraSwipeDist", 100); m_options.adb_extra_swipe_duration = options_json.get("adbExtraSwipeDuration", -1); m_options.penguin_report.cmd_format = options_json.get("penguinReport", "cmdFormat", std::string()); - - if (options_json.contains("aipOcr")) { - auto& aip_ocr = options_json.at("aipOcr"); - m_options.aip_ocr.enable = aip_ocr.get("enable", false); - m_options.aip_ocr.accurate = aip_ocr.get("accurate", false); - m_options.aip_ocr.client_id = aip_ocr.get("clientId", std::string()); - m_options.aip_ocr.client_secret = aip_ocr.get("clientSerect", std::string()); - } m_options.depot_export_template.ark_planner = options_json.get("depotExportTemplate", "arkPlanner", std::string()); } diff --git a/src/MeoAssistant/GeneralConfiger.h b/src/MeoAssistant/GeneralConfiger.h index c41786ab4d..1b9cf0bf33 100644 --- a/src/MeoAssistant/GeneralConfiger.h +++ b/src/MeoAssistant/GeneralConfiger.h @@ -11,14 +11,6 @@ namespace asst { - struct AipOcrCfg // 百度 OCR API 的配置 - { - bool enable = false; - bool accurate = false; // 是否使用高精度识别 - std::string client_id; - std::string client_secret; - }; - struct PenguinReportCfg // 企鹅物流数据汇报 的配置 { std::string cmd_format; // 命令格式 @@ -38,7 +30,6 @@ namespace asst int adb_extra_swipe_dist = 0; // 额外的滑动距离:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来 int adb_extra_swipe_duration = -1; // 额外的滑动持续时间:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来。若小于0,则关闭额外滑动功能 PenguinReportCfg penguin_report; // 企鹅数据汇报:每次到结算界面,汇报掉落数据至企鹅数据 https://penguin-stats.cn/ - AipOcrCfg aip_ocr; // 百度 OCR API 的配置 DepotExportTemplate depot_export_template; // 仓库识别结果导出模板 }; diff --git a/src/MeoAssistant/InfrastConfiger.cpp b/src/MeoAssistant/InfrastConfiger.cpp index fc67284ad5..899c00c9db 100644 --- a/src/MeoAssistant/InfrastConfiger.cpp +++ b/src/MeoAssistant/InfrastConfiger.cpp @@ -10,7 +10,7 @@ bool asst::InfrastConfiger::parse(const json::value& json) for (const json::value& facility : json.at("facility").as_array()) { std::string facility_name = facility.as_string(); - json::value facility_json = json.at(facility_name); + const json::value& facility_json = json.at(facility_name); std::vector products; for (const json::value& product_json : facility_json.at("products").as_array()) { diff --git a/src/MeoAssistant/Logger.hpp b/src/MeoAssistant/Logger.hpp index d6f206f4d8..590308051c 100644 --- a/src/MeoAssistant/Logger.hpp +++ b/src/MeoAssistant/Logger.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -143,13 +144,13 @@ namespace asst #else // ! _WIN32 sprintf(buff, "[%s][%s][Px%x][Tx%x]", asst::utils::get_format_time().c_str(), - level.data(), getpid(), std::this_thread::get_id() + level.data(), getpid(), unsigned (std::hash{}(std::this_thread::get_id())) ); #endif // END _WIN32 if (!m_ofs.is_open()) { m_ofs = std::ofstream(m_log_filename, std::ios::out | std::ios::app); - } + } #ifdef ASST_DEBUG stream_args(m_ofs, buff, args...); #else @@ -159,7 +160,7 @@ namespace asst #ifdef ASST_DEBUG stream_args(std::cout, buff, std::forward(args)...); #endif - } + } template inline void stream_args(std::ostream& os, T&& first, Args&&... rest) @@ -192,7 +193,7 @@ namespace asst os << first << " "; // Don't fucking use gbk in linux #endif } -}; + }; inline static std::string m_dirname; std::mutex m_trace_mutex; @@ -202,8 +203,8 @@ namespace asst class LoggerAux { public: - LoggerAux(const std::string& func_name) - : m_func_name(func_name), + LoggerAux(std::string func_name) + : m_func_name(std::move(func_name)), m_start_time(std::chrono::steady_clock::now()) { Logger::get_instance().trace(m_func_name, " | enter"); diff --git a/src/MeoAssistant/MatchImageAnalyzer.cpp b/src/MeoAssistant/MatchImageAnalyzer.cpp index 9387ce0db5..0732ae140c 100644 --- a/src/MeoAssistant/MatchImageAnalyzer.cpp +++ b/src/MeoAssistant/MatchImageAnalyzer.cpp @@ -8,8 +8,7 @@ asst::MatchImageAnalyzer::MatchImageAnalyzer(const cv::Mat& image, const Rect& r : AbstractImageAnalyzer(image, roi), m_templ_name(std::move(templ_name)), m_templ_thres(templ_thres) -{ -} +{} bool asst::MatchImageAnalyzer::analyze() { diff --git a/src/MeoAssistant/MeoAssistant.vcxproj b/src/MeoAssistant/MeoAssistant.vcxproj index 383191ac27..d35224f08d 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj +++ b/src/MeoAssistant/MeoAssistant.vcxproj @@ -70,7 +70,6 @@ - @@ -152,7 +151,6 @@ - @@ -186,6 +184,7 @@ + diff --git a/src/MeoAssistant/MeoAssistant.vcxproj.filters b/src/MeoAssistant/MeoAssistant.vcxproj.filters index c4f7fa3240..4b62fdbed2 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj.filters +++ b/src/MeoAssistant/MeoAssistant.vcxproj.filters @@ -46,12 +46,6 @@ {c600668a-0d70-4694-b1e5-6a78cffd0b1a} - - {a5c43813-70af-4b0e-94ac-9ddecc2e4a3c} - - - {6cd85910-b561-4cf3-85a2-20be3cd1dd76} - {de865908-2cef-4993-b3ab-1e20290d97b9} @@ -201,9 +195,6 @@ 头文件\Task\Sub\Infrast - - 头文件\Requester - 头文件\Task\Plugin @@ -449,9 +440,6 @@ 源文件\Task\Sub\Infrast - - 源文件\Requester - 源文件\Task\Plugin @@ -607,5 +595,8 @@ 资源文件 + + 资源文件 + \ No newline at end of file diff --git a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp index 8fb805e614..2849e33131 100644 --- a/src/MeoAssistant/MultiMatchImageAnalyzer.cpp +++ b/src/MeoAssistant/MultiMatchImageAnalyzer.cpp @@ -10,8 +10,7 @@ asst::MultiMatchImageAnalyzer::MultiMatchImageAnalyzer(const cv::Mat& image, con : AbstractImageAnalyzer(image, roi), m_templ_name(std::move(templ_name)), m_templ_thres(templ_thres) -{ -} +{} bool asst::MultiMatchImageAnalyzer::analyze() { diff --git a/src/MeoAssistant/OcrImageAnalyzer.cpp b/src/MeoAssistant/OcrImageAnalyzer.cpp index c5013f9a8a..ba7b3be165 100644 --- a/src/MeoAssistant/OcrImageAnalyzer.cpp +++ b/src/MeoAssistant/OcrImageAnalyzer.cpp @@ -5,7 +5,6 @@ #include "Logger.hpp" #include "Resource.h" -#include "AipOcr.h" bool asst::OcrImageAnalyzer::analyze() { @@ -58,36 +57,25 @@ bool asst::OcrImageAnalyzer::analyze() return true; }; - auto& aip_ocr = Resrc.cfg().get_options().aip_ocr; - bool need_local = true; - if (aip_ocr.enable) { - if (aip_ocr.accurate) { - need_local = !AipOcr::get_instance().request_ocr_accurate(m_image, m_ocr_result, all_pred); - } - else { - need_local = !AipOcr::get_instance().request_ocr_general(m_image, m_ocr_result, all_pred); - } + if (m_roi.x < 0) { + Log.warn("roi is out of range", m_roi.to_string()); + m_roi.x = 0; + } + if (m_roi.y < 0) { + Log.warn("roi is out of range", m_roi.to_string()); + m_roi.y = 0; + } + if (m_roi.x + m_roi.width > m_image.cols) { + Log.warn("roi is out of range", m_roi.to_string()); + m_roi.width = m_image.cols - m_roi.x; + } + if (m_roi.y + m_roi.height > m_image.rows) { + Log.warn("roi is out of range", m_roi.to_string()); + m_roi.height = m_image.rows - m_roi.y; } - if (need_local) { - if (m_roi.x < 0) { - Log.warn("roi is out of range", m_roi.to_string()); - m_roi.x = 0; - } - if (m_roi.y < 0) { - Log.warn("roi is out of range", m_roi.to_string()); - m_roi.y = 0; - } - if (m_roi.x + m_roi.width > m_image.cols) { - Log.warn("roi is out of range", m_roi.to_string()); - m_roi.width = m_image.cols - m_roi.x; - } - if (m_roi.y + m_roi.height > m_image.rows) { - Log.warn("roi is out of range", m_roi.to_string()); - m_roi.height = m_image.rows - m_roi.y; - } - m_ocr_result = Resrc.ocr().recognize(m_image, m_roi, all_pred, m_without_det); - } + m_ocr_result = Resrc.ocr().recognize(m_image, m_roi, all_pred, m_without_det); + //log.trace("ocr result", m_ocr_result); return !m_ocr_result.empty(); } diff --git a/src/MeoAssistant/OcrWithFlagTemplImageAnalyzer.h b/src/MeoAssistant/OcrWithFlagTemplImageAnalyzer.h index 2d69593fbb..490c2dc914 100644 --- a/src/MeoAssistant/OcrWithFlagTemplImageAnalyzer.h +++ b/src/MeoAssistant/OcrWithFlagTemplImageAnalyzer.h @@ -21,7 +21,7 @@ namespace asst virtual const std::vector& get_result() const noexcept override; virtual std::vector& get_result() noexcept override; - void set_task_info(const std::string& templ_task_name, const std::string& ocr_task_name); + void set_task_info(const std::string& templ_task_name, const std::string& ocr_task_name); // FIXME: hiding virtual function void set_flag_rect_move(Rect flag_rect_move); protected: diff --git a/src/MeoAssistant/PackageTask.cpp b/src/MeoAssistant/PackageTask.cpp index 31050e3afb..3e14f72691 100644 --- a/src/MeoAssistant/PackageTask.cpp +++ b/src/MeoAssistant/PackageTask.cpp @@ -6,7 +6,7 @@ bool asst::PackageTask::run() { if (!m_enable) { - Log.info("task is disable, pass", basic_info().to_string()); + Log.info("task disabled, pass", basic_info().to_string()); return true; } m_runned = true; diff --git a/src/MeoAssistant/ProcessTask.cpp b/src/MeoAssistant/ProcessTask.cpp index 0816a79a5f..d64a8d7872 100644 --- a/src/MeoAssistant/ProcessTask.cpp +++ b/src/MeoAssistant/ProcessTask.cpp @@ -33,7 +33,7 @@ asst::ProcessTask::ProcessTask(AbstractTask&& abs, std::vector task bool asst::ProcessTask::run() { if (!m_enable) { - Log.info("task is disable, pass", basic_info().to_string()); + Log.info("task disabled, pass", basic_info().to_string()); return true; } if (m_task_delay == TaskDelayUnsetted) { diff --git a/src/MeoAssistant/README.md b/src/MeoAssistant/README.md index 60a5e6f055..2502a8138b 100644 --- a/src/MeoAssistant/README.md +++ b/src/MeoAssistant/README.md @@ -7,21 +7,26 @@ - ~~窗口控制、点击、截图,使用Win32 Api~~ 新版本已全面使用Adb控制 - 部分不响应句柄消息的模拟器,使用Adb控制 -## 识别及解析 +## 开源库 - 图像识别库:[opencv](https://github.com/opencv/opencv.git) - ~~文字识别库:[chineseocr_lite](https://github.com/DayBreak-u/chineseocr_lite.git)~~ - 文字识别库:[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) - ~~关卡掉落识别:[企鹅物流识别](https://github.com/penguin-statistics/recognizer)~~ +- 地图格子识别:[Arknights-Tile-Pos](https://github.com/yuanyan3060/Arknights-Tile-Pos) - C++ JSON库:[meojson](https://github.com/MistEO/meojson.git) - C++ 运算符解析器:[calculator](https://github.com/kimwalisch/calculator) -- C++ base64编解码:[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64) +- ~~C++ base64编解码:[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64)~~ - C++ 解压压缩库:[zlib](https://github.com/madler/zlib) - C++ Gzip封装:[gzip-hpp](https://github.com/mapbox/gzip-hpp) +- WPF MVVW框架:[Stylet](https://github.com/canton7/Stylet) +- WPF控件库:[HandyControl](https://github.com/HandyOrg/HandyControl) +- C# JSON库: [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) +- 下载器:[aria2](https://github.com/aria2/aria2) -## 数据 +## 数据源 -- 公开招募数据:[明日方舟工具箱](https://www.bigfun.cn/tools/aktools/hr) +- ~~公开招募数据:[明日方舟工具箱](https://www.bigfun.cn/tools/aktools/hr)~~ - 干员及基建数据:[PRTS明日方舟中文WIKI](http://prts.wiki/) - 关卡数据:[企鹅物流数据统计](https://penguin-stats.cn/) - 材料数据:[明日方舟bot常用素材](https://github.com/yuanyan3060/Arknights-Bot-Resource) diff --git a/src/MeoAssistant/RecruitCalcTask.cpp b/src/MeoAssistant/RecruitCalcTask.cpp index dcdae980e8..1502c0ba53 100644 --- a/src/MeoAssistant/RecruitCalcTask.cpp +++ b/src/MeoAssistant/RecruitCalcTask.cpp @@ -66,8 +66,7 @@ bool RecruitCalcTask::_run() static const std::string Robot = "支援机械"; auto robot_iter = all_tags_name.find(Robot); if (robot_iter != all_tags_name.end()) { - if (m_skip_robot) - { + if (m_skip_robot) { info["what"] = "RecruitRobotTag"; info["details"] = json::object{ { "tag", Robot } diff --git a/src/MeoAssistant/Resource.cpp b/src/MeoAssistant/Resource.cpp index e30502d999..13ef64d208 100644 --- a/src/MeoAssistant/Resource.cpp +++ b/src/MeoAssistant/Resource.cpp @@ -22,7 +22,7 @@ bool asst::Resource::load(const std::string& dir) constexpr static auto InfrastCfgFilename = "infrast.json"; constexpr static auto InfrastTempls = "template/infrast"; //constexpr static const char* CopilotCfgDirname = "copilot"; - constexpr static auto RoguelikeCfgDirname = "roguelike"; + constexpr static auto RoguelikeCfgDirname = "roguelike_copilot.json"; constexpr static auto OcrResourceFilename = "PaddleOCR"; constexpr static auto TilesCalcResourceFilename = "Arknights-Tile-Pos"; constexpr static auto StageDropsCfgFilename = "stages.json"; @@ -91,17 +91,15 @@ bool asst::Resource::load(const std::string& dir) // } //} - if (std::filesystem::exists(dir + RoguelikeCfgDirname)) { - for (const auto& entry : std::filesystem::directory_iterator(dir + RoguelikeCfgDirname)) { - if (entry.path().extension() != ".json") { - continue; - } - if (!m_roguelike_cfg_unique_ins.load(entry.path().string())) { - m_last_error = entry.path().string() + " Load failed"; - return false; - } + if (!m_roguelike_cfg_unique_ins.load(dir + RoguelikeCfgDirname)) { + if (!m_loaded) { + m_last_error = std::string(RoguelikeCfgDirname) + ": " + m_roguelike_cfg_unique_ins.get_last_error(); + return false; } } + else { + overload = true; + } if (!m_infrast_cfg_unique_ins.load(dir + InfrastCfgFilename)) { if (!m_loaded) { diff --git a/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp b/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp index cd10ecae8a..53bff854c8 100644 --- a/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp +++ b/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp @@ -1,6 +1,7 @@ #include "RoguelikeBattleTaskPlugin.h" #include +#include #include "BattleImageAnalyzer.h" #include "Controller.h" @@ -46,8 +47,6 @@ bool asst::RoguelikeBattleTaskPlugin::_run() speed_up(); - constexpr static auto time_limit = std::chrono::minutes(10); - bool timeout = false; auto start_time = std::chrono::steady_clock::now(); while (!need_exit()) { @@ -55,7 +54,8 @@ bool asst::RoguelikeBattleTaskPlugin::_run() if (!auto_battle() && m_opers_used) { break; } - if (std::chrono::steady_clock::now() - start_time > time_limit) { + using namespace std::chrono_literals; + if (std::chrono::steady_clock::now() - start_time > 5min) { timeout = true; break; } @@ -85,7 +85,7 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() constexpr int StageNameRetryTimes = 50; for (int i = 0; i != StageNameRetryTimes; ++i) { cv::Mat image = m_ctrler->get_image(); - OcrImageAnalyzer name_analyzer(image); + OcrWithPreprocessImageAnalyzer name_analyzer(image); name_analyzer.set_task_info(stage_name_task_ptr); if (!name_analyzer.analyze()) { @@ -106,8 +106,6 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() if (calced) { break; } - // 有些性能非常好的电脑,加载画面很快;但如果使用了不兼容 gzip 的方式截图的模拟器,截图反而非常慢 - // 这种时候一共可供识别的也没几帧,还要考虑识别错的情况。所以这里不能 sleep std::this_thread::yield(); } } @@ -117,29 +115,41 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() calced = true; } - if (calced) { -#ifdef ASST_DEBUG - auto normal_tiles = tile.calc(m_stage_name, true); - cv::Mat draw = cv::imread("j.png"); - for (const auto& [point, info] : normal_tiles) { - using TileKey = TilePack::TileKey; - static const std::unordered_map TileKeyMapping = { - { TileKey::Invalid, "invalid" }, - { TileKey::Forbidden, "forbidden" }, - { TileKey::Wall, "wall" }, - { TileKey::Road, "road" }, - { TileKey::Home, "end" }, - { TileKey::EnemyHome, "start" }, - { TileKey::Floor, "floor" }, - { TileKey::Hole, "hole" }, - { TileKey::Telin, "telin" }, - { TileKey::Telout, "telout" } - }; - - cv::putText(draw, TileKeyMapping.at(info.key), cv::Point(info.pos.x, info.pos.y), 1, 1, cv::Scalar(0, 0, 255)); + auto opt = Resrc.roguelike().get_stage_data(m_stage_name); + if (opt && !opt->replacement_home.empty()) { + m_homes = opt->replacement_home; + std::string log_str = "[ "; + for (auto& home : m_homes) { + if (m_normal_tile_info.find(home) == m_normal_tile_info.end()) { + Log.error("No replacement home point", home.x, home.y); + } + log_str += "( " + std::to_string(home.x) + ", " + std::to_string(home.y) + " ), "; } -#endif + log_str += "]"; + Log.info("replacement home:", log_str); + } + else { + for (const auto& [loc, side] : m_normal_tile_info) { + if (side.key == TilePack::TileKey::Home) { + m_homes.emplace_back(loc); + } + } + } + if (opt && !opt->key_kills.empty()) { + std::string log_str = "[ "; + for (const auto& kills : opt->key_kills) { + m_key_kills.emplace(kills); + log_str += std::to_string(kills) + ", "; + } + log_str += "]"; + Log.info("key kills:", log_str); + } + if (m_homes.empty()) { + Log.error("Unknown home pos"); + } + + if (calced) { auto cb_info = basic_info_with_what("StageInfo"); auto& details = cb_info["details"]; details["name"] = m_stage_name; @@ -157,8 +167,6 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() { LogTraceFunction; - using BattleRealTimeOper = asst::BattleRealTimeOper; - //if (int hp = battle_analyzer.get_hp(); // hp != 0) { // bool used_skills = false; @@ -210,15 +218,42 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() // 点击当前最合适的干员 BattleRealTimeOper opt_oper; bool oper_found = false; + + // 一个人都没有的时候,先下个地面单位(如果有的话) + // 第二个人下奶(如果有的话) + bool wait_melee = false; + bool wait_medic = false; + if (m_used_tiles.empty() || m_used_tiles.size() == 1) { + for (const auto& op : opers) { + if (m_used_tiles.empty()) { + if (op.role == BattleRole::Warrior || + op.role == BattleRole::Pioneer || + op.role == BattleRole::Tank) { + wait_melee = true; + } + } + else if (m_used_tiles.size() == 1) { + if (op.role == BattleRole::Medic) { + wait_medic = true; + } + } + } + } + for (auto role : RoleOrder) { - // 一个人都没有的时候,先下个地面单位 - if (m_used_tiles.empty()) { + if (wait_melee) { if (role != BattleRole::Warrior && role != BattleRole::Pioneer && role != BattleRole::Tank) { continue; } } + else if (wait_medic) { + if (role != BattleRole::Medic) { + continue; + } + } + for (const auto& oper : opers) { if (!oper.available) { continue; @@ -252,57 +287,20 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() oper_name = oper_name_analyzer.get_result().front().text; } - // 将干员拖动到场上 - auto loc = Loc::All; - switch (opt_oper.role) { - case BattleRole::Medic: - case BattleRole::Support: - case BattleRole::Sniper: - case BattleRole::Caster: - loc = Loc::Ranged; - break; - case BattleRole::Pioneer: - case BattleRole::Warrior: - case BattleRole::Tank: - loc = Loc::Melee; - break; - case BattleRole::Special: - case BattleRole::Drone: - default: - // 特种和无人机,有的只能放地面,有的又只能放高台,不好判断 - // 笨办法,都试试,总有一次能成的 - //{ - // static Loc static_loc = Loc::Melee; - // loc = static_loc; - // if (static_loc == Loc::Melee) { - // static_loc = Loc::Ranged; - // } - // else { - // static_loc = Loc::Melee; - // } - //} - loc = Loc::Melee; - break; - } + // 计算最优部署位置及方向 + const auto& [placed_loc, direction] = calc_best_plan(opt_oper.role); - Point placed_loc = get_placed(loc); + // 将干员拖动到场上 Point placed_point = m_side_tile_info.at(placed_loc).pos; -#ifdef ASST_DEBUG - auto draw_image = m_ctrler->get_image(); - cv::circle(draw_image, cv::Point(placed_point.x, placed_point.y), 10, cv::Scalar(0, 0, 255), -1); -#endif Rect placed_rect(placed_point.x, placed_point.y, 1, 1); int dist = static_cast( - std::sqrt( - (std::abs(placed_point.x - opt_oper.rect.x) << 1) - + (std::abs(placed_point.y - opt_oper.rect.y) << 1))); - int duration = static_cast(swipe_oper_task_ptr->pre_delay / 800.0 * dist); // 随便取的一个系数 - m_ctrler->swipe(opt_oper.rect, placed_rect, duration, true, 0); + std::sqrt( + (std::pow(std::abs(placed_point.x - opt_oper.rect.x), 2)) + + (std::pow(std::abs(placed_point.y - opt_oper.rect.y), 2)))); + // 1000 是随便取的一个系数,把整数的 pre_delay 转成小数用的 + int duration = static_cast(swipe_oper_task_ptr->pre_delay / 800.0 * dist * log10(dist)); m_ctrler->swipe(opt_oper.rect, placed_rect, duration, true, 0); sleep(use_oper_task_ptr->rear_delay); - // 计算往哪边拖动(干员朝向) - Point direction = calc_direction(placed_loc, opt_oper.role); - // 将方向转换为实际的 swipe end 坐标点 Point end_point = placed_point; constexpr int coeff = 500; @@ -325,8 +323,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() bool asst::RoguelikeBattleTaskPlugin::speed_up() { - ProcessTask task(*this, { "Roguelike1BattleSpeedUp" }); - return task.run(); + return ProcessTask(*this, { "Roguelike1BattleSpeedUp" }).run(); } bool asst::RoguelikeBattleTaskPlugin::use_skill(const asst::Rect& rect) @@ -367,10 +364,14 @@ void asst::RoguelikeBattleTaskPlugin::clear() m_opers_used = false; m_pre_hp = 0; m_homes.clear(); + decltype(m_key_kills) empty; + m_key_kills.swap(empty); m_cur_home_index = 0; m_stage_name.clear(); m_side_tile_info.clear(); m_used_tiles.clear(); + m_kills = 0; + m_total_kills = 0; for (auto& [key, status] : m_restore_status) { m_status->set_number(key, status); @@ -380,6 +381,10 @@ void asst::RoguelikeBattleTaskPlugin::clear() bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image) { + if (!check_key_kills(image)) { + return false; + } + auto task_ptr = Task.get("BattleAutoSkillFlag"); const Rect& skill_roi_move = task_ptr->rect_move; @@ -406,7 +411,12 @@ bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image) continue; } m_ctrler->click(pos_rect); - used |= ProcessTask(*this, { "BattleSkillReadyOnClick" }).set_task_delay(0).run(); + sleep(Task.get("BattleUseOper")->pre_delay); + bool ret = ProcessTask(*this, { "BattleSkillReadyOnClick" }).set_retry_times(2).run(); + if (!ret) { + ProcessTask(*this, { "BattleCancelSelection" }).set_retry_times(0).run(); + } + used |= ret; if (usage == BattleSkillUsage::Once) { m_status->set_number(status_key, static_cast(BattleSkillUsage::OnceUsed)); m_restore_status[status_key] = static_cast(BattleSkillUsage::Once); @@ -415,13 +425,35 @@ bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image) return used; } +bool asst::RoguelikeBattleTaskPlugin::check_key_kills(const cv::Mat& image) +{ + if (m_key_kills.empty()) { + return true; + } + int need_kills = m_key_kills.front(); + + BattleImageAnalyzer analyzer(image); + if (m_total_kills) { + analyzer.set_pre_total_kills(m_total_kills); + } + analyzer.set_target(BattleImageAnalyzer::Target::Kills); + if (analyzer.analyze()) { + m_kills = analyzer.get_kills(); + m_total_kills = analyzer.get_total_kills(); + if (m_kills >= need_kills) { + m_key_kills.pop(); + return true; + } + } + return false; +} + bool asst::RoguelikeBattleTaskPlugin::wait_start() { auto start_time = std::chrono::system_clock::now(); auto check_time = [&]() -> bool { - auto now = std::chrono::system_clock::now(); - auto diff = now - start_time; - return diff.count() > std::chrono::seconds(60).count(); + using namespace std::chrono_literals; + return std::chrono::system_clock::now() - start_time > 1min; }; MatchImageAnalyzer officially_begin_analyzer; @@ -447,10 +479,28 @@ bool asst::RoguelikeBattleTaskPlugin::wait_start() std::this_thread::yield(); } - sleep(Task.get("BattleWaitingToLoad")->rear_delay); + std::filesystem::create_directory("map"); + for (const auto& [loc, info] : m_normal_tile_info) { + std::string text = "( " + std::to_string(loc.x) + ", " + std::to_string(loc.y) + " )"; + cv::putText(image, text, cv::Point(info.pos.x - 30, info.pos.y), 1, 1.2, cv::Scalar(0, 0, 255), 2); + } + + // 识别一帧总击杀数 + BattleImageAnalyzer kills_analyzer(image); + kills_analyzer.set_target(BattleImageAnalyzer::Target::Kills); + if (kills_analyzer.analyze()) { + m_kills = kills_analyzer.get_kills(); + m_total_kills = kills_analyzer.get_total_kills(); + } + +#ifdef WIN32 + cv::imwrite("map/" + utils::utf8_to_ansi(m_stage_name) + ".png", image); +#else + cv::imwrite("map/" + m_stage_name + ".png", image); +#endif return true; -} + } //asst::Rect asst::RoguelikeBattleTaskPlugin::get_placed_by_cv() //{ @@ -465,61 +515,109 @@ bool asst::RoguelikeBattleTaskPlugin::wait_start() // return placed_rect; //} -asst::Point asst::RoguelikeBattleTaskPlugin::get_placed(Loc buildable_type) +asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::calc_best_plan(BattleRole role) { - LogTraceFunction; - - if (m_homes.empty()) { - for (const auto& [loc, side] : m_side_tile_info) { - if (side.key == TilePack::TileKey::Home) { - m_homes.emplace_back(loc); - } - } - if (m_homes.empty()) { - Log.error("Unknown home pos"); - } + auto buildable_type = Loc::All; + switch (role) { + case BattleRole::Medic: + case BattleRole::Support: + case BattleRole::Sniper: + case BattleRole::Caster: + buildable_type = Loc::Ranged; + break; + case BattleRole::Pioneer: + case BattleRole::Warrior: + case BattleRole::Tank: + buildable_type = Loc::Melee; + break; + case BattleRole::Special: + case BattleRole::Drone: + default: + //// 特种和无人机,有的只能放地面,有的又只能放高台,不好判断 + //// 笨办法,都试试,总有一次能成的 + //{ + // static Loc static_loc = Loc::Melee; + // loc = static_loc; + // if (static_loc == Loc::Melee) { + // static_loc = Loc::Ranged; + // } + // else { + // static_loc = Loc::Melee; + // } + //} + // 大部分无人机都是可以摆在地上的,摆烂了 + buildable_type = Loc::Melee; + break; } + if (m_cur_home_index >= m_homes.size()) { m_cur_home_index = 0; } - Point nearest; - int min_dist = INT_MAX; - int min_dy = INT_MAX; - - Point home(5, 5); // 默认值,一般是地图的中间 + Point home(5, 5); // 实在找不到家门了,随便取个点当家门用算了,一般是地图的中间 if (m_cur_home_index < m_homes.size()) { home = m_homes.at(m_cur_home_index); } + auto comp_dist = [&](const Point& lhs, const Point& rhs) -> bool { + int lhs_y_dist = std::abs(lhs.y - home.y); + int lhs_dist = std::abs(lhs.x - home.x) + lhs_y_dist; + int rhs_y_dist = std::abs(rhs.y - home.y); + int rhs_dist = std::abs(rhs.x - home.x) + rhs_y_dist; + // 距离一样选择 x 轴上的,因为一般的地图都是横向的长方向 + return lhs_dist == rhs_dist ? lhs_y_dist < rhs_y_dist : lhs_dist < rhs_dist; + }; + + // 把所有可用的点按距离排个序 + std::vector available_locations; for (const auto& [loc, tile] : m_normal_tile_info) { - if (tile.buildable == buildable_type - || tile.buildable == Loc::All) { - if (m_used_tiles.find(loc) != m_used_tiles.cend()) { - continue; - } - int dx = std::abs(home.x - loc.x); - int dy = std::abs(home.y - loc.y); - int dist = dx * dx + dy * dy; - if (dist < min_dist) { - min_dist = dist; - min_dy = dy; - nearest = loc; - } - // 距离一样选择 x 轴上的,因为一般的地图都是横向的长方向 - else if (dist == min_dist && dy < min_dy) { - min_dist = dist; - min_dy = dy; - nearest = loc; - } + if ((tile.buildable == buildable_type || tile.buildable == Loc::All) + && m_used_tiles.find(loc) == m_used_tiles.cend()) { + available_locations.emplace_back(loc); } } - Log.info(__FUNCTION__, nearest.to_string()); + if (available_locations.empty()) { + Log.error("No available locations"); + if (m_used_tiles.empty()) { + Log.error("No used tiles"); + return DeployInfo(); + } + m_used_tiles.clear(); + return calc_best_plan(role); + } - return nearest; + std::sort(available_locations.begin(), available_locations.end(), comp_dist); + + // 取距离最近的N个点,计算分数。然后使用得分最高的点 + constexpr int CalcPointCount = 4; + if (available_locations.size() > CalcPointCount) { + available_locations.erase(available_locations.begin() + CalcPointCount, available_locations.end()); + } + + Point best_location; + Point best_direction; + int max_score = INT_MIN; + + const auto& near_loc = available_locations.at(0); + int min_dist = std::abs(near_loc.x - home.x) + std::abs(near_loc.y - home.y); + + for (const auto& loc : available_locations) { + auto cur_result = calc_best_direction_and_score(loc, role); + // 离得远的要扣分 + constexpr int DistWeights = -1050; + int extra_dist = std::abs(loc.x - home.x) + std::abs(loc.y - home.y) - min_dist; + int extra_dist_score = DistWeights * extra_dist; + + if (cur_result.second + extra_dist_score > max_score) { + max_score = cur_result.second; + best_location = loc; + best_direction = cur_result.first; + } + } + return { best_location, best_direction }; } -asst::Point asst::RoguelikeBattleTaskPlugin::calc_direction(Point loc, BattleRole role) +std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score(Point loc, BattleRole role) { LogTraceFunction; @@ -602,17 +700,17 @@ asst::Point asst::RoguelikeBattleTaskPlugin::calc_direction(Point loc, BattleRol Point(0, 1), Point(1, 1), Point(2, 1), Point(3, 1), }; break; - case BattleRole::Special: case BattleRole::Warrior: - case BattleRole::Tank: - case BattleRole::Pioneer: attack_range = { - Point(0, 0), Point(1, 0), + Point(0, 0), Point(1, 0), Point(2, 0) }; break; + case BattleRole::Special: + case BattleRole::Tank: + case BattleRole::Pioneer: case BattleRole::Drone: attack_range = { - Point(0, 0) + Point(0, 0), Point(1, 0) }; break; } @@ -623,10 +721,10 @@ asst::Point asst::RoguelikeBattleTaskPlugin::calc_direction(Point loc, BattleRol static const Point UpDirection(0, -1); std::unordered_map> DirectionAttackRangeMap = { - { RightDirection, std::vector() }, - { DownDirection, std::vector() }, - { LeftDirection, std::vector() }, { UpDirection, std::vector() }, + { RightDirection, std::vector() }, + { LeftDirection, std::vector() }, + { DownDirection, std::vector() }, }; for (auto& [direction, cor_attack_range] : DirectionAttackRangeMap) { @@ -677,12 +775,14 @@ asst::Point asst::RoguelikeBattleTaskPlugin::calc_direction(Point loc, BattleRol { TileKey::Forbidden, 0 }, { TileKey::Wall, 500 }, { TileKey::Road, 1000 }, - { TileKey::Home, 800 }, - { TileKey::EnemyHome, 800 }, + { TileKey::Home, 500 }, + { TileKey::EnemyHome, 900 }, { TileKey::Floor, 1000 }, { TileKey::Hole, 0 }, - { TileKey::Telin, 0 }, - { TileKey::Telout, 0 } + { TileKey::Telin, 700 }, + { TileKey::Telout, 800 }, + { TileKey::Volcano, 1000 }, + { TileKey::Healing, 1000 }, }; // 治疗干员朝向的权重 static const std::unordered_map TileKeyMedicWeights = { @@ -695,7 +795,9 @@ asst::Point asst::RoguelikeBattleTaskPlugin::calc_direction(Point loc, BattleRol { TileKey::Floor, 0 }, { TileKey::Hole, 0 }, { TileKey::Telin, 0 }, - { TileKey::Telout, 0 } + { TileKey::Telout, 0 }, + { TileKey::Volcano, 1000 }, + { TileKey::Healing, 1000 }, }; switch (role) { @@ -735,5 +837,5 @@ asst::Point asst::RoguelikeBattleTaskPlugin::calc_direction(Point loc, BattleRol } } - return opt_direction; + return std::make_pair(opt_direction, max_score); } diff --git a/src/MeoAssistant/RoguelikeBattleTaskPlugin.h b/src/MeoAssistant/RoguelikeBattleTaskPlugin.h index c52bb1d7d1..51fcddb173 100644 --- a/src/MeoAssistant/RoguelikeBattleTaskPlugin.h +++ b/src/MeoAssistant/RoguelikeBattleTaskPlugin.h @@ -30,22 +30,30 @@ namespace asst bool retreat(const Point& point); void clear(); bool try_possible_skill(const cv::Mat& image); + bool check_key_kills(const cv::Mat& image); bool wait_start(); - // 通过资源文件离线计算可放置干员的位置,优先使用 - // 返回 可格子的位置 - Point get_placed(Loc buildable_type); + struct DeployInfo + { + Point placed; + Point direction; + }; + + DeployInfo calc_best_plan(BattleRole role); // 计算摆放干员的朝向 - // 返回滑动的方向 - Point calc_direction(Point loc, BattleRole role); + // 返回滑动的方向、得分 + std::pair calc_best_direction_and_score(Point loc, BattleRole role); bool m_opers_used = false; int m_pre_hp = 0; + int m_kills = 0; + int m_total_kills = 0; std::unordered_map m_side_tile_info; std::unordered_map m_normal_tile_info; std::vector m_homes; + std::queue m_key_kills; size_t m_cur_home_index = 0; std::unordered_map m_used_tiles; std::unordered_map m_restore_status; diff --git a/src/MeoAssistant/RoguelikeCopilotConfiger.cpp b/src/MeoAssistant/RoguelikeCopilotConfiger.cpp index e85a85a515..70fdbb433c 100644 --- a/src/MeoAssistant/RoguelikeCopilotConfiger.cpp +++ b/src/MeoAssistant/RoguelikeCopilotConfiger.cpp @@ -4,158 +4,34 @@ #include "Logger.hpp" +std::optional asst::RoguelikeCopilotConfiger::get_stage_data(const std::string& stage_name) const +{ + auto it = m_stage_data.find(stage_name); + if (it == m_stage_data.end()) { + return std::nullopt; + } + return it->second; +} + bool asst::RoguelikeCopilotConfiger::parse(const json::value& json) { - std::string stage_name = json.at("stage_name").as_string(); - - std::vector actions_vec; - - for (const auto& action_info : json.at("actions").as_array()) { - RoguelikeBattleAction action; - - static const std::unordered_map ActionTypeMapping = { - { "Deploy", BattleActionType::Deploy }, - { "DEPLOY", BattleActionType::Deploy }, - { "deploy", BattleActionType::Deploy }, - { "部署", BattleActionType::Deploy }, - - { "Skill", BattleActionType::UseSkill }, - { "SKILL", BattleActionType::UseSkill }, - { "skill", BattleActionType::UseSkill }, - { "技能", BattleActionType::UseSkill }, - - { "Retreat", BattleActionType::Retreat }, - { "RETREAT", BattleActionType::Retreat }, - { "retreat", BattleActionType::Retreat }, - { "撤退", BattleActionType::Retreat }, - - { "AllSkill", BattleActionType::UseAllSkill }, - { "allskill", BattleActionType::UseAllSkill }, - { "ALLSKILL", BattleActionType::UseAllSkill }, - { "Allskill", BattleActionType::UseAllSkill }, - { "全部技能", BattleActionType::UseAllSkill }, - }; - - std::string type_str = action_info.get("type", "Deploy"); - - if (auto iter = ActionTypeMapping.find(type_str); - iter != ActionTypeMapping.end()) { - action.type = iter->second; - } - else { - action.type = BattleActionType::Deploy; - } - action.kills = action_info.get("kills", 0); - - if (action_info.contains("roles")) { - for (const auto& role_info : action_info.at("roles").as_array()) { - static const std::unordered_map RoleMapping = { - { "MEDIC", BattleRole::Medic }, - { "Medic", BattleRole::Medic }, - { "medic", BattleRole::Medic }, - { "医疗", BattleRole::Medic }, - - { "WARRIOR", BattleRole::Warrior }, - { "Warrior", BattleRole::Warrior }, - { "warrior", BattleRole::Warrior }, - { "近卫", BattleRole::Warrior }, - - { "SPECIAL", BattleRole::Special }, - { "Special", BattleRole::Special }, - { "special", BattleRole::Special }, - { "特种", BattleRole::Special }, - - { "SNIPER", BattleRole::Sniper }, - { "Sniper", BattleRole::Sniper }, - { "sniper", BattleRole::Sniper }, - { "狙击", BattleRole::Sniper }, - - { "PIONEER", BattleRole::Pioneer }, - { "Pioneer", BattleRole::Pioneer }, - { "pioneer", BattleRole::Pioneer }, - { "先锋", BattleRole::Pioneer }, - - { "TANK", BattleRole::Tank }, - { "Tank", BattleRole::Tank }, - { "tank", BattleRole::Tank }, - { "重装", BattleRole::Tank }, - - { "SUPPORT", BattleRole::Support }, - { "Support", BattleRole::Support }, - { "support", BattleRole::Support }, - { "辅助", BattleRole::Support }, - - { "CASTER", BattleRole::Caster }, - { "Caster", BattleRole::Caster }, - { "caster", BattleRole::Caster }, - { "术师", BattleRole::Caster }, - - { "Drone", BattleRole::Drone }, - { "DRONE", BattleRole::Drone }, - { "drone", BattleRole::Drone }, - { "召唤物", BattleRole::Drone }, - { "无人机", BattleRole::Drone } - }; - - std::string role_name = role_info.as_string(); - if (auto iter = RoleMapping.find(role_name); - iter != RoleMapping.end()) { - action.roles.emplace_back(iter->second); - } - else { - Log.error("Unknown role", role_name); - } + for (const auto& stage_info : json.as_array()) { + std::string stage_name = stage_info.at("stage_name").as_string(); + RoguelikeBattleData data; + data.stage_name = stage_name; + if (stage_info.contains("replacement_home")) { + for (const auto& points : stage_info.at("replacement_home").as_array()) { + const auto& points_array = points.as_array(); + Point point(points_array.at(0).as_integer(), points_array.at(1).as_integer()); + data.replacement_home.push_back(point); } } - - action.location.x = action_info.get("location", 0, 0); - action.location.y = action_info.get("location", 1, 0); - - static const std::unordered_map DeployDirectionMapping = { - { "Right", BattleDeployDirection::Right }, - { "RIGHT", BattleDeployDirection::Right }, - { "right", BattleDeployDirection::Right }, - { "右", BattleDeployDirection::Right }, - - { "Left", BattleDeployDirection::Left }, - { "LEFT", BattleDeployDirection::Left }, - { "left", BattleDeployDirection::Left }, - { "左", BattleDeployDirection::Left }, - - { "Up", BattleDeployDirection::Up }, - { "UP", BattleDeployDirection::Up }, - { "up", BattleDeployDirection::Up }, - { "上", BattleDeployDirection::Up }, - - { "Down", BattleDeployDirection::Down }, - { "DOWN", BattleDeployDirection::Down }, - { "down", BattleDeployDirection::Down }, - { "下", BattleDeployDirection::Down }, - - { "None", BattleDeployDirection::None }, - { "NONE", BattleDeployDirection::None }, - { "none", BattleDeployDirection::None }, - { "无", BattleDeployDirection::None }, - }; - - std::string direction_str = action_info.get("direction", "Right"); - - if (auto iter = DeployDirectionMapping.find(direction_str); - iter != DeployDirectionMapping.end()) { - action.direction = iter->second; + if (stage_info.contains("key_kills")) { + for (const auto& key_kill : stage_info.at("key_kills").as_array()) { + data.key_kills.emplace_back(key_kill.as_integer()); + } } - else { - action.direction = BattleDeployDirection::Right; - } - action.waiting_cost = action_info.get("waiting_cost", false); - action.pre_delay = action_info.get("pre_delay", 0); - action.rear_delay = action_info.get("rear_delay", 0); - action.time_out = action_info.get("timeout", INT_MAX); - - actions_vec.emplace_back(std::move(action)); + m_stage_data.emplace(std::move(stage_name), std::move(data)); } - - m_battle_actions[std::move(stage_name)] = std::move(actions_vec); - return true; } diff --git a/src/MeoAssistant/RoguelikeCopilotConfiger.h b/src/MeoAssistant/RoguelikeCopilotConfiger.h index 6b8ff714e2..4b5842b8f7 100644 --- a/src/MeoAssistant/RoguelikeCopilotConfiger.h +++ b/src/MeoAssistant/RoguelikeCopilotConfiger.h @@ -1,6 +1,8 @@ #pragma once #include "AbstractConfiger.h" +#include + #include "AsstBattleDef.h" namespace asst @@ -10,17 +12,10 @@ namespace asst public: virtual ~RoguelikeCopilotConfiger() = default; - bool contains_actions(const std::string& stage_name) const noexcept - { - return m_battle_actions.find(stage_name) != m_battle_actions.cend(); - } + std::optional get_stage_data(const std::string& stage_name) const; - auto get_actions(const std::string& stage_name) const noexcept - { - return m_battle_actions.at(stage_name); - } protected: virtual bool parse(const json::value& json) override; - std::unordered_map> m_battle_actions; + std::unordered_map m_stage_data; }; } diff --git a/src/MeoAssistant/RoguelikeRecruitImageAnalyzer.cpp b/src/MeoAssistant/RoguelikeRecruitImageAnalyzer.cpp index dcd9175172..3273287408 100644 --- a/src/MeoAssistant/RoguelikeRecruitImageAnalyzer.cpp +++ b/src/MeoAssistant/RoguelikeRecruitImageAnalyzer.cpp @@ -82,6 +82,6 @@ int asst::RoguelikeRecruitImageAnalyzer::match_level(const Rect& raw_roi) return 0; } - std::string level = analyzer.get_result().front().text; + std::string level = analyzer.get_result().front().text; return std::stoi(level); } diff --git a/src/MeoAssistant/RoguelikeRecruitTaskPlugin.cpp b/src/MeoAssistant/RoguelikeRecruitTaskPlugin.cpp index c25f7122f8..b65136e0f5 100644 --- a/src/MeoAssistant/RoguelikeRecruitTaskPlugin.cpp +++ b/src/MeoAssistant/RoguelikeRecruitTaskPlugin.cpp @@ -39,19 +39,32 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() recruited = true; }; - for (const auto& info : analyzer.get_result()) { - if (info.elite == 0 || - (info.elite == 1 && info.level < 50)) { + const auto& oper_list = analyzer.get_result(); + for (const auto& info : oper_list) { + // 先看看有没有精二的 + if (info.elite != 2) { continue; } recruit_oper(info); break; } + if (!recruited) { + for (const auto& info : oper_list) { + // 拿个精一 50 以上的 + if (info.elite == 0 || + (info.elite == 1 && info.level < 50)) { + continue; + } + recruit_oper(info); + break; + } + } + if (!recruited) { Log.info("All are lower"); // 随便招个精一的 - for (const auto& info : analyzer.get_result()) { + for (const auto& info : oper_list) { if (info.elite == 0) { continue; } @@ -63,7 +76,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() if (!recruited) { // 随便招个 Log.info("All are very lower"); - auto info = analyzer.get_result().front(); + auto info = oper_list.front(); recruit_oper(info); } diff --git a/src/MeoAssistant/StageDropsConfiger.cpp b/src/MeoAssistant/StageDropsConfiger.cpp index acc76310ae..e6284599c5 100644 --- a/src/MeoAssistant/StageDropsConfiger.cpp +++ b/src/MeoAssistant/StageDropsConfiger.cpp @@ -4,36 +4,36 @@ bool asst::StageDropsConfiger::parse(const json::value& json) { - for (const json::value& stage_json : json.as_array()) { - if (!stage_json.contains("dropInfos")) { // 这种一般是以前的活动关,现在已经关闭了的 - continue; - } - std::string code = stage_json.at("code").as_string(); // 关卡名,举例 1-7,可能重复(例如磨难和普通是同一个关卡名) - std::string stage_id = stage_json.at("stageId").as_string(); // 关卡id,举例 main_01-07,上传用的,唯一 - StageDifficulty difficulty = (stage_id.find("tough_") == 0) ? StageDifficulty::Tough : StageDifficulty::Normal; - StageKey key{ code, difficulty }; + for (const json::value& stage_json : json.as_array()) { + if (!stage_json.contains("dropInfos")) { // 这种一般是以前的活动关,现在已经关闭了的 + continue; + } + std::string code = stage_json.at("code").as_string(); // 关卡名,举例 1-7,可能重复(例如磨难和普通是同一个关卡名) + std::string stage_id = stage_json.at("stageId").as_string(); // 关卡id,举例 main_01-07,上传用的,唯一 + StageDifficulty difficulty = (stage_id.find("tough_") == 0) ? StageDifficulty::Tough : StageDifficulty::Normal; + StageKey key{ code, difficulty }; - StageInfo info; - info.stage_id = stage_id; - info.ap_cost = stage_json.get("apCost", 0); - for (const json::value& drops_json : stage_json.at("dropInfos").as_array()) { - static const std::unordered_map TypeMapping = { - { "NORMAL_DROP", StageDropType::Normal }, - { "EXTRA_DROP", StageDropType::Extra }, - { "FURNITURE", StageDropType::Furniture }, - { "SPECIAL_DROP", StageDropType::Special } - }; - StageDropType type = TypeMapping.at(drops_json.at("dropType").as_string()); - std::string item_id = drops_json.get("itemId", std::string()); - if (item_id.empty()) { - continue; - } - info.drops[type].emplace_back(item_id); - m_all_item_id.emplace(item_id); - } + StageInfo info; + info.stage_id = stage_id; + info.ap_cost = stage_json.get("apCost", 0); + for (const json::value& drops_json : stage_json.at("dropInfos").as_array()) { + static const std::unordered_map TypeMapping = { + { "NORMAL_DROP", StageDropType::Normal }, + { "EXTRA_DROP", StageDropType::Extra }, + { "FURNITURE", StageDropType::Furniture }, + { "SPECIAL_DROP", StageDropType::Special } + }; + StageDropType type = TypeMapping.at(drops_json.at("dropType").as_string()); + std::string item_id = drops_json.get("itemId", std::string()); + if (item_id.empty()) { + continue; + } + info.drops[type].emplace_back(item_id); + m_all_item_id.emplace(item_id); + } - m_all_stage_code.emplace(code); - m_stage_info.emplace(std::move(key), std::move(info)); - } - return true; -} \ No newline at end of file + m_all_stage_code.emplace(code); + m_stage_info.emplace(std::move(key), std::move(info)); + } + return true; +} diff --git a/src/MeoAssistant/TaskData.cpp b/src/MeoAssistant/TaskData.cpp index d6ebabc49b..aa46fe6ae0 100644 --- a/src/MeoAssistant/TaskData.cpp +++ b/src/MeoAssistant/TaskData.cpp @@ -9,7 +9,7 @@ #include "TemplResource.h" #include "Logger.hpp" -const std::shared_ptr asst::TaskData::get(const std::string& name) const noexcept +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()) { @@ -35,7 +35,7 @@ bool asst::TaskData::parse(const json::value& json) LogTraceFunction; auto to_lower = [](char c) -> char { - return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c; + return char(std::tolower(c)); }; for (const auto& [name, task_json] : json.as_object()) { std::string algorithm_str = task_json.get("algorithm", "matchtemplate"); diff --git a/src/MeoAssistant/TaskData.h b/src/MeoAssistant/TaskData.h index adbe5d0259..590c1075d0 100644 --- a/src/MeoAssistant/TaskData.h +++ b/src/MeoAssistant/TaskData.h @@ -18,14 +18,14 @@ namespace asst virtual ~TaskData() = default; - static TaskData& get_instance() + static TaskData& get_instance() noexcept { static TaskData unique_instance; return unique_instance; } const std::unordered_set& get_templ_required() const noexcept; - const std::shared_ptr get(const std::string& name) const noexcept; + std::shared_ptr get(const std::string& name) const noexcept; std::shared_ptr get(const std::string& name); protected: diff --git a/src/MeoAssistant/TemplResource.h b/src/MeoAssistant/TemplResource.h index af2a6c2281..6f919580a8 100644 --- a/src/MeoAssistant/TemplResource.h +++ b/src/MeoAssistant/TemplResource.h @@ -9,24 +9,24 @@ namespace asst { - class TemplResource : public AbstractResource - { - public: + class TemplResource : public AbstractResource + { + public: - virtual ~TemplResource() = default; + virtual ~TemplResource() = default; - void set_load_required(std::unordered_set required) noexcept; - virtual bool load(const std::string& dir) override; + void set_load_required(std::unordered_set required) noexcept; + 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; + bool exist_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); + void emplace_templ(std::string key, cv::Mat templ); - private: - std::unordered_set m_templs_filename; - std::unordered_map m_templs; + private: + std::unordered_set m_templs_filename; + std::unordered_map m_templs; - bool m_loaded = false; - }; + bool m_loaded = false; + }; } diff --git a/src/MeoAssistant/TilePack.cpp b/src/MeoAssistant/TilePack.cpp index daa68ab9c7..69d3dd0efa 100644 --- a/src/MeoAssistant/TilePack.cpp +++ b/src/MeoAssistant/TilePack.cpp @@ -54,7 +54,9 @@ std::unordered_map asst::TilePack::calc( { "tile_floor", TileKey::Floor }, { "tile_hole", TileKey::Hole }, { "tile_telin", TileKey::Telin }, - { "tile_telout", TileKey::Telout } + { "tile_telout", TileKey::Telout }, + { "tile_volcano", TileKey::Volcano }, + { "tile_healing", TileKey::Healing }, }; for (size_t y = 0; y < pos.size(); ++y) { diff --git a/src/MeoAssistant/TilePack.h b/src/MeoAssistant/TilePack.h index 129ae87c41..877db7601d 100644 --- a/src/MeoAssistant/TilePack.h +++ b/src/MeoAssistant/TilePack.h @@ -39,8 +39,10 @@ namespace asst EnemyHome, // 红门(可能还有其他的情况) Floor, // 不能放干员,但敌人可以走 Hole, // 空降兵掉下去的地方( - Telin, // 不知道是啥 - Telout // 不知道是啥 + Telin, // 传送门入口 + Telout, // 传送门出口 + Volcano, // 岩浆地块,可以放干员,敌人也可以走,但是会持续掉血 + Healing // 治疗地块,可以放干员,敌人也可以走,会给干员回血 }; struct TileInfo diff --git a/src/MeoAsstGui/Bootstrapper.cs b/src/MeoAsstGui/Bootstrapper.cs index e924cf9e3f..1f259d15b7 100644 --- a/src/MeoAsstGui/Bootstrapper.cs +++ b/src/MeoAsstGui/Bootstrapper.cs @@ -9,11 +9,12 @@ // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Threading; +using Microsoft.Toolkit.Uwp.Notifications; using Stylet; using StyletIoC; -using Microsoft.Toolkit.Uwp.Notifications; namespace MeoAsstGui { @@ -38,8 +39,14 @@ namespace MeoAsstGui protected override void OnExit(ExitEventArgs e) { // MessageBox.Show("O(∩_∩)O 拜拜"); + //关闭程序时清理操作中心中的通知 - ToastNotificationManagerCompat.History.Clear(); + var os = RuntimeInformation.OSDescription.ToString(); + if (os.ToString().CompareTo("Microsoft Windows 10.0.10240") >= 0) + { + ToastNotificationManagerCompat.History.Clear(); + } + ViewStatusStorage.Save(); } diff --git a/src/MeoAsstGui/Helper/AsstProxy.cs b/src/MeoAsstGui/Helper/AsstProxy.cs index 3f7edc8d31..36c93fa068 100644 --- a/src/MeoAsstGui/Helper/AsstProxy.cs +++ b/src/MeoAsstGui/Helper/AsstProxy.cs @@ -95,7 +95,7 @@ namespace MeoAsstGui loaded = AsstLoadResource(System.IO.Directory.GetCurrentDirectory() + "\\resource\\global\\YoStarEN"); _curResource = "YoStarEN"; } - // 这种是手贱看到美服点了一下,又点回官服的 + // 这种是手贱看国际服点了一下,又点回官服的 else if (_curResource.Length != 0 && (settingsModel.ClientType == "Official" || settingsModel.ClientType == "Bilibili" || settingsModel.ClientType == String.Empty)) { @@ -126,7 +126,6 @@ namespace MeoAsstGui var mainModel = _container.Get(); mainModel.Idle = true; var settingsModel = _container.Get(); - Execute.OnUIThread(async () => { var task = Task.Run(() => @@ -209,6 +208,8 @@ namespace MeoAsstGui } } + private bool connected = false; + private void procConnectInfo(JObject details) { var what = details["what"].ToString(); @@ -217,19 +218,23 @@ namespace MeoAsstGui switch (what) { case "Connected": + connected = true; svm.ConnectAddress = details["details"]["address"].ToString(); break; case "UnsupportedResolution": + connected = false; mainModel.AddLog("分辨率过低,请设置为 720p 或更高", "darkred"); break; case "ResolutionError": + connected = false; mainModel.AddLog("分辨率获取失败,建议重启电脑,或更换模拟器后再试", "darkred"); break; case "Disconnect": case "CommandExecFailed": + connected = false; mainModel.AddLog("错误!连接断开!", "darkred"); AsstStop(); break; @@ -287,7 +292,8 @@ namespace MeoAsstGui toast.Show(); } copilotModel.Idle = true; - mainModel.CheckAndShutdown(); + //mainModel.CheckAndShutdown(); + mainModel.CheckAfterComplete(); break; } } @@ -424,7 +430,7 @@ namespace MeoAsstGui break; case "Roguelike1StageEncounterEnter": - mainModel.AddLog("关卡:不期而遇/古堡馈赠"); + mainModel.AddLog("关卡:不期而遇"); break; //case "Roguelike1StageBoonsEnter": @@ -647,6 +653,10 @@ namespace MeoAsstGui // copilotModel.AddLog(subTaskDetails["details"].ToString(), details_color.Length == 0 ? "dark" : details_color); //} break; + + case "UnsupportedLevel": + copilotModel.AddLog("不支持的关卡\n请更新 MAA 软件版本,或检查作业文件", "darkred"); + break; } } @@ -698,7 +708,7 @@ namespace MeoAsstGui } } - public bool AsstConnect(ref string error) + public bool AsstConnect(ref string error, bool firsttry = false) { if (!LoadGlobalResource()) { @@ -706,6 +716,11 @@ namespace MeoAsstGui return false; } + if (connected) + { + return true; + } + var settings = _container.Get(); if (settings.AdbPath == String.Empty || settings.ConnectAddress == String.Empty) @@ -734,7 +749,9 @@ namespace MeoAsstGui } if (!ret) { - error = "连接失败\n请检查连接设置"; + if (firsttry) + error = "连接失败\n正在尝试启动模拟器"; + else error = "连接失败\n请检查连接设置"; } return ret; } diff --git a/src/MeoAsstGui/Helper/AutoScroll.cs b/src/MeoAsstGui/Helper/AutoScroll.cs index b41f768f66..93b530ca9b 100644 --- a/src/MeoAsstGui/Helper/AutoScroll.cs +++ b/src/MeoAsstGui/Helper/AutoScroll.cs @@ -1,4 +1,4 @@ -// MeoAsstGui - A part of the MeoAssistantArknights project +// MeoAsstGui - A part of the MeoAssistantArknights project // Copyright (C) 2021 MistEO and Contributors // // This program is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ namespace MeoAsstGui public static class AutoScroll { private static bool _autoScroll; + public static bool GetAutoScroll(DependencyObject obj) { return (bool)obj.GetValue(AutoScrollProperty); diff --git a/src/MeoAsstGui/Helper/ToastNotification.cs b/src/MeoAsstGui/Helper/ToastNotification.cs index bde99fb52b..b07ea784ae 100644 --- a/src/MeoAsstGui/Helper/ToastNotification.cs +++ b/src/MeoAsstGui/Helper/ToastNotification.cs @@ -20,18 +20,17 @@ using System.Windows; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; +using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Win32; using Notification.Wpf; using Notification.Wpf.Base; using Notification.Wpf.Constants; using Notification.Wpf.Controls; -using Microsoft.Toolkit.Uwp.Notifications; namespace MeoAsstGui { public class ToastNotification : IDisposable { - public bool CheckToastSystem() { return Convert.ToBoolean(ViewStatusStorage.Get("Toast.UsingSystem", bool.FalseString)); @@ -207,6 +206,7 @@ namespace MeoAsstGui protected string _buttonSystemText = null; protected string _buttonSystemUrl; + public string ButtonSystemUrl { get { return _buttonSystemUrl; } @@ -214,6 +214,7 @@ namespace MeoAsstGui } protected bool _buttonSystemEnabled = Convert.ToBoolean(bool.FalseString); + #endregion 通知按钮变量 /// @@ -316,7 +317,7 @@ namespace MeoAsstGui /// 内容显示行数,如果内容太多建议使用 ShowMore() /// 播放提示音 /// 通知内容 - /// + /// public void Show(double lifeTime = 10d, uint row = 1, NotificationSounds sound = NotificationSounds.Notification, NotificationContent notificationContent = null) @@ -425,7 +426,7 @@ namespace MeoAsstGui sound: NotificationSounds.Notification, notificationContent: content); } - + /// /// 显示更新版本的通知 /// @@ -440,6 +441,7 @@ namespace MeoAsstGui sound: NotificationSounds.Notification, notificationContent: content); } + #endregion 显示通知方法 #endregion 通知显示 diff --git a/src/MeoAsstGui/Helper/WinAdapter.cs b/src/MeoAsstGui/Helper/WinAdapter.cs index 4d52b194f2..54e71cd5d4 100644 --- a/src/MeoAsstGui/Helper/WinAdapter.cs +++ b/src/MeoAsstGui/Helper/WinAdapter.cs @@ -11,11 +11,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Diagnostics; using System.IO; +using System.Linq; namespace MeoAsstGui { diff --git a/src/MeoAsstGui/MeoAsstGui.csproj b/src/MeoAsstGui/MeoAsstGui.csproj index cfc22def59..e6d0e8566f 100644 --- a/src/MeoAsstGui/MeoAsstGui.csproj +++ b/src/MeoAsstGui/MeoAsstGui.csproj @@ -263,7 +263,7 @@ 3.3.0 - 6.0.3 + 6.0.4 7.1.2 @@ -284,7 +284,7 @@ 6.1.0.5 - 2.1.0 + 2.2.0 1.3.6 diff --git a/src/MeoAsstGui/UserControl/AboutUserControl.xaml b/src/MeoAsstGui/UserControl/AboutUserControl.xaml index 403e8cfa71..9eef8d67bb 100644 --- a/src/MeoAsstGui/UserControl/AboutUserControl.xaml +++ b/src/MeoAsstGui/UserControl/AboutUserControl.xaml @@ -11,7 +11,7 @@ d:DesignHeight="300" d:DesignWidth="550"> - MAA官网 + MAA 官网 源码: GitHub @@ -20,6 +20,9 @@ 自动战斗作业分享 Q 群 + 自动战斗作业分享网站 + + 常见问题 diff --git a/src/MeoAsstGui/UserControl/FightSettingsUserControl.xaml.cs b/src/MeoAsstGui/UserControl/FightSettingsUserControl.xaml.cs index 6c3890ba77..85c97432f2 100644 --- a/src/MeoAsstGui/UserControl/FightSettingsUserControl.xaml.cs +++ b/src/MeoAsstGui/UserControl/FightSettingsUserControl.xaml.cs @@ -9,11 +9,7 @@ // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY -using System.IO; -using System.Collections.ObjectModel; using System.Windows.Controls; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace MeoAsstGui { diff --git a/src/MeoAsstGui/UserControl/StartSettingsUserControl.xaml b/src/MeoAsstGui/UserControl/StartSettingsUserControl.xaml index 23af617aaf..1723c56861 100644 --- a/src/MeoAsstGui/UserControl/StartSettingsUserControl.xaml +++ b/src/MeoAsstGui/UserControl/StartSettingsUserControl.xaml @@ -21,20 +21,6 @@ - - - - - - - - - -