Merge pull request #1928 from MaaAssistantArknights/chore/recruitment_resource

feat: Localize recruitment results and make resources auto updated
This commit is contained in:
MistEO
2022-09-27 16:38:28 +08:00
committed by GitHub
20 changed files with 6258 additions and 7143 deletions

View File

@@ -126,7 +126,8 @@ MAA 的意思是 MAA Assistant Arknights
- ~~公开招募数据:[明日方舟工具箱](https://www.bigfun.cn/tools/aktools/hr)~~
- 干员及基建数据:[PRTS明日方舟中文WIKI](http://prts.wiki/)
- 关卡数据:[企鹅物流数据统计](https://penguin-stats.cn/)
- 材料数据[明日方舟bot常用素材](https://github.com/yuanyan3060/Arknights-Bot-Resource)
- 游戏数据及資源[明日方舟bot常用素材](https://github.com/yuanyan3060/Arknights-Bot-Resource)
- 游戏数据:[《明日方舟》游戏数据](https://github.com/Kengxxiao/ArknightsGameData)
### 贡献/参与者

View File

@@ -127,6 +127,7 @@ Due to the small number of overseas clients users and the lack of project manpow
- Operators and infrastructure data: [PRTS Arknights Wiki (Chinese)](http://prts.wiki/)
- Stage data: [Penguin Stats](https://penguin-stats.io/)
- Gamedata and resources: [Arknights Bot Resource](https://github.com/yuanyan3060/Arknights-Bot-Resource)
- Gamedata: [Arknights Game Data](https://github.com/Kengxxiao/ArknightsGameData)
### Contributors

View File

@@ -129,7 +129,8 @@ UIを見れば使い方もすぐ分かる
- ~~公開求人データ:[アークナイツツール](https://www.bigfun.cn/tools/aktools/hr)~~
- オペレーターおよび基地データ:[PRTSアークナイツ中国語WIKI](http://prts.wiki/)
- ステージデータ:[PenguinStatsデータ統計](https://penguin-stats.io/)
- ゲームデータとリソース:[アークナイツbotリソース](https://github.com/yuanyan3060/Arknights-Bot-Resource)
- ゲームデータとリソース:[アークナイツbotリソース](https://github.com/yuanyan3060/Arknights-Bot-Resource)
- ゲームデータ:[アークナイツのゲームデータ](https://github.com/Kengxxiao/ArknightsGameData)
### 貢献/協力者

View File

@@ -126,7 +126,8 @@ MAA 的意思是 MAA Assistant Arknights
- ~~公開招募資料:[明日方舟工具箱](https://www.bigfun.cn/tools/aktools/hr)~~
- 幹員及基建資料:[PRTS明日方舟中文WIKI](http://prts.wiki/)
- 關卡資料:[企鵝物流資料統計](https://penguin-stats.cn/)
- 素材資料[明日方舟bot常用素材](https://github.com/yuanyan3060/Arknights-Bot-Resource)
- 遊戲數據及資源[明日方舟bot常用素材](https://github.com/yuanyan3060/Arknights-Bot-Resource)
- 遊戲數據:[《明日方舟》游戏数据](https://github.com/Kengxxiao/ArknightsGameData)
### 貢獻/參與者

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1274
resource/recruitment.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -357,9 +357,9 @@ asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc
bool has_robot_tag = false;
json::value info = basic_info();
std::vector<json::value> tag_json_vector;
ranges::transform(tags, std::back_inserter(tag_json_vector), std::mem_fn(&TextRect::text));
info["details"]["tags"] = json::array(tag_json_vector);
std::vector<std::string> tag_name_vector;
ranges::transform(tags, std::back_inserter(tag_name_vector), std::mem_fn(&TextRect::text));
info["details"]["tags"] = json::array(get_tag_names(tag_name_vector));
// tags result
{
@@ -375,7 +375,7 @@ asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc
has_special_tag = true;
json::value cb_info = info;
cb_info["what"] = "RecruitSpecialTag";
cb_info["details"]["tag"] = *special_iter;
cb_info["details"]["tag"] = RecruitData.get_tag_name(*special_iter);
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
@@ -386,7 +386,7 @@ asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc
has_robot_tag = true;
json::value cb_info = info;
cb_info["what"] = "RecruitSpecialTag";
cb_info["details"]["tag"] = *robot_iter;
cb_info["details"]["tag"] = RecruitData.get_tag_name(*robot_iter);
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
@@ -436,12 +436,7 @@ asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc
std::vector<json::value> result_json_vector;
for (const auto& comb : result_vec) {
json::value comb_json;
std::vector<json::value> tags_json_vector;
for (const std::string& tag : comb.tags) {
tags_json_vector.emplace_back(tag);
}
comb_json["tags"] = json::array(std::move(tags_json_vector));
comb_json["tags"] = json::array(get_tag_names(comb.tags));
std::vector<json::value> opers_json_vector;
for (const RecruitOperInfo& oper_info : ranges::reverse_view(comb.opers)) { // print reversely
@@ -573,7 +568,7 @@ asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc
{
json::value cb_info = basic_info();
cb_info["what"] = "RecruitTagsSelected";
cb_info["details"] = json::object { { "tags", json::array(final_combination.tags) } };
cb_info["details"] = json::object { { "tags", json::array(get_tag_names(final_combination.tags)) } };
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
@@ -682,6 +677,15 @@ bool asst::AutoRecruitTask::hire_all()
return true;
}
std::vector<std::string> asst::AutoRecruitTask::get_tag_names(const std::vector<RecruitConfiger::TagId>& ids) const
{
std::vector<RecruitConfiger::TagId> names;
for (const std::string& id : ids) {
names.emplace_back(RecruitData.get_tag_name(id));
}
return names;
}
void asst::AutoRecruitTask::upload_result(const json::value& details)
{
LogTraceFunction;

View File

@@ -1,12 +1,13 @@
#pragma once
#include "AbstractTask.h"
#include "AsstTypes.h"
#include <optional>
#include <set>
#include <vector>
#include "AsstTypes.h"
#include "RecruitConfiger.h"
namespace asst
{
class ReportDataTask;
@@ -45,6 +46,7 @@ namespace asst
bool hire_all(const cv::Mat&);
bool hire_all();
bool initialize_dirty_slot_info(const cv::Mat&);
std::vector<std::string> get_tag_names(const std::vector<RecruitConfiger::TagId>& ids) const;
static std::vector<TextRect> start_recruit_analyze(const cv::Mat& image);
void upload_result(const json::value& details);

View File

@@ -6,31 +6,34 @@
#include "Logger.hpp"
std::string asst::RecruitConfiger::get_tag_name(const TagId& id) const noexcept
{
auto iter = m_all_tags_name.find(id);
if (iter == m_all_tags_name.cend()) {
return "Unknown tag";
}
return iter->second;
}
bool asst::RecruitConfiger::parse(const json::value& json)
{
clear();
for (const json::value& oper : json.as_array()) {
for (const json::value& oper : json.at("operators").as_array()) {
RecruitOperInfo oper_temp;
oper_temp.name = oper.at("name").as_string();
oper_temp.type = oper.at("type").as_string();
m_all_types.emplace(oper_temp.type);
// 职业类型也作为tag之一加上"干员"两个字
std::string type_as_tag = oper_temp.type + "干员";
oper_temp.tags.emplace(type_as_tag);
m_all_tags.emplace(std::move(type_as_tag));
oper_temp.level = oper.at("level").as_integer();
oper_temp.sex = oper.get("sex", "unknown");
oper_temp.level = oper.at("rarity").as_integer();
for (const json::value& tag_value : oper.at("tags").as_array()) {
std::string tag = tag_value.as_string();
oper_temp.tags.emplace(tag);
m_all_tags.emplace(std::move(tag));
}
oper_temp.hidden = oper.get("hidden", false);
oper_temp.name_en = oper.get("name-en", "unknown");
m_all_opers.emplace_back(std::move(oper_temp));
}
for (const auto& [id, name] : json.at("tags").as_object()) {
m_all_tags_name.emplace(id, name);
}
// 按干员等级排个序
ranges::sort(m_all_opers, std::greater {}, std::mem_fn(&RecruitOperInfo::level));
@@ -44,5 +47,5 @@ void asst::RecruitConfiger::clear()
m_all_opers.clear();
m_all_tags.clear();
m_all_types.clear();
m_all_tags_name.clear();
}

View File

@@ -17,12 +17,8 @@ namespace asst
struct RecruitOperInfo
{
std::string name;
std::string type;
int level = 0;
std::string sex;
std::unordered_set<std::string> tags;
bool hidden = false;
std::string name_en;
bool has_tag(const std::string& tag) const { return tags.contains(tag); }
@@ -85,12 +81,15 @@ namespace asst
class RecruitConfiger final : public SingletonHolder<RecruitConfiger>, public AbstractConfiger
{
public:
using TagId = std::string;
public:
virtual ~RecruitConfiger() override = default;
static constexpr int CorrectNumberOfTags = 5;
const std::unordered_set<std::string>& get_all_tags() const noexcept { return m_all_tags; }
const std::vector<RecruitOperInfo>& get_all_opers() const noexcept { return m_all_opers; }
std::string get_tag_name(const TagId& id) const noexcept;
protected:
virtual bool parse(const json::value& json) override;
@@ -98,9 +97,8 @@ namespace asst
void clear();
std::unordered_set<std::string> m_all_tags;
std::unordered_set<std::string> m_all_types;
std::vector<RecruitOperInfo> m_all_opers;
std::unordered_map<TagId, std::string> m_all_tags_name;
};
inline static auto& RecruitData = RecruitConfiger::get_instance();
} // namespace asst

View File

@@ -50,7 +50,7 @@ bool asst::ResourceLoader::load(const std::filesystem::path& path)
/* load resource with json files*/
LoadResourceAndCheckRet(GeneralConfiger, "config.json"_p);
LoadResourceAndCheckRet(RecruitConfiger, "recruit.json"_p);
LoadResourceAndCheckRet(RecruitConfiger, "recruitment.json"_p);
LoadResourceAndCheckRet(StageDropsConfiger, "stages.json"_p);
LoadResourceAndCheckRet(RoguelikeCopilotConfiger, "roguelike_copilot.json"_p);
LoadResourceAndCheckRet(RoguelikeRecruitConfiger, "roguelike_recruit.json"_p);

View File

@@ -3,8 +3,8 @@
#include <unordered_set>
#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable: 5054 )
#pragma warning(push)
#pragma warning(disable : 5054)
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"
@@ -15,25 +15,72 @@
#endif
#include <opencv2/opencv.hpp>
#ifdef _MSC_VER
#pragma warning( pop )
#pragma warning(pop)
#elif defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#include <ranges>
#include <meojson/json.hpp>
#include <ranges>
std::string ReplaceAll(std::string str, const std::string& from, const std::string& to)
{
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
}
return str;
}
std::vector<std::string> Split(const std::string& str, const std::string& delim)
{
std::vector<std::string> tokens;
size_t prev = 0, pos = 0;
do {
pos = str.find(delim, prev);
if (pos == std::string::npos) pos = str.length();
std::string token = str.substr(prev, pos - prev);
if (!token.empty()) {
tokens.push_back(token);
}
prev = pos + delim.length();
} while (pos < str.length() && prev < str.length());
return tokens;
}
static inline void ltrim(std::string& s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); }));
}
// trim from end (in place)
static inline void rtrim(std::string& s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string& s)
{
ltrim(s);
rtrim(s);
}
bool update_items_data(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir);
bool cvt_single_item_template(const std::filesystem::path& input, const std::filesystem::path& output);
bool update_infrast_data(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir);
bool update_stages_data(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir);
bool update_roguelike_recruit(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir, const std::filesystem::path& solution_dir);
bool update_roguelike_recruit(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir,
const std::filesystem::path& solution_dir);
bool update_infrast_templates(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir);
bool generate_english_roguelike_stage_name_replacement(const std::filesystem::path& ch_file, const std::filesystem::path& en_file);
bool generate_english_roguelike_stage_name_replacement(const std::filesystem::path& ch_file,
const std::filesystem::path& en_file);
bool update_battle_chars_info(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir);
bool update_recruitment_data(const std::filesystem::path& input_dir, const std::filesystem::path& output, bool is_base);
int main([[maybe_unused]] int argc, char** argv)
{
@@ -44,7 +91,8 @@ int main([[maybe_unused]] int argc, char** argv)
std::cout << "------------Update Arknights-Bot-Resource------------" << std::endl;
std::string git_cmd;
if (!std::filesystem::exists(input_dir)) {
git_cmd = "git clone https://github.com/yuanyan3060/Arknights-Bot-Resource.git --depth=1 \"" + input_dir.string() + "\"";
git_cmd = "git clone https://github.com/yuanyan3060/Arknights-Bot-Resource.git --depth=1 \"" +
input_dir.string() + "\"";
}
else {
git_cmd = "git -C \"" + input_dir.string() + "\" pull";
@@ -68,10 +116,9 @@ int main([[maybe_unused]] int argc, char** argv)
/* Update levels.json from Arknights-Bot-Resource*/
std::cout << "------------Update levels.json------------" << std::endl;
if (!std::filesystem::copy_file(
input_dir / "levels.json",
third_resource_dir / "Arknights-Tile-Pos" / "levels.json",
std::filesystem::copy_options::overwrite_existing)) {
if (!std::filesystem::copy_file(input_dir / "levels.json",
third_resource_dir / "Arknights-Tile-Pos" / "levels.json",
std::filesystem::copy_options::overwrite_existing)) {
std::cerr << "update levels.json failed" << std::endl;
return -1;
}
@@ -94,26 +141,63 @@ int main([[maybe_unused]] int argc, char** argv)
}
///* Update roguelike recruit data from Arknights-Bot-Resource*/
//std::cout << "------------Update roguelike recruit data------------" << std::endl;
//if (!update_roguelike_recruit(input_dir, resource_dir, solution_dir)) {
// std::cerr << "Update roguelike recruit data failed" << std::endl;
// return -1;
//}
// std::cout << "------------Update roguelike recruit data------------" << std::endl;
// if (!update_roguelike_recruit(input_dir, resource_dir, solution_dir)) {
// std::cerr << "Update roguelike recruit data failed" << std::endl;
// return -1;
// }
/* Update stage.json from Penguin Stats*/
std::cout << "------------Update stage.json------------" << std::endl;
if (!update_stages_data(cur_path, solution_dir / "resource")) {
if (!update_stages_data(cur_path, resource_dir)) {
std::cerr << "Update stages data failed" << std::endl;
return -1;
}
/* Update battle chars info from Arknights-Bot-Resource*/
std::cout << "------------Update battle chars info------------" << std::endl;
if (!update_battle_chars_info(input_dir, solution_dir / "resource")) {
if (!update_battle_chars_info(input_dir, resource_dir)) {
std::cerr << "Update battle chars info failed" << std::endl;
return -1;
}
/* Git update ArknightsGameData */
std::cout << "------------Update ArknightsGameData------------" << std::endl;
const std::filesystem::path game_data_dir = cur_path / "ArknightsGameData";
if (!std::filesystem::exists(game_data_dir)) {
git_cmd =
"git clone https://github.com/Kengxxiao/ArknightsGameData.git --depth=1 \"" + game_data_dir.string() + "\"";
}
else {
git_cmd = "git -C \"" + game_data_dir.string() + "\" pull";
}
git_ret = system(git_cmd.c_str());
if (git_ret != 0) {
std::cout << "git cmd failed" << std::endl;
return -1;
}
/* Update recruitment data from ArknightsGameData*/
std::cout << "------------Update recruitment data------------" << std::endl;
if (!update_recruitment_data(game_data_dir / "zh_CN" / "gamedata" / "excel", resource_dir / "recruitment.json",
true)) {
std::cerr << "Update recruitment data failed" << std::endl;
return -1;
}
std::unordered_map<std::string, std::string> recruitment_dirs = {
{ "en_US", "YoStarEN" }, { "ja_JP", "YoStarJP" }, { "ko_KR", "YoStarKR" }, { "zh_TW", "txwy" }
};
for (const auto& [in, out] : recruitment_dirs) {
std::cout << "------------Update recruitment data for " << out << "------------" << std::endl;
if (!update_recruitment_data(game_data_dir / in / "gamedata" / "excel",
resource_dir / "global" / out / "resource" / "recruitment.json", false)) {
std::cerr << "Update recruitment data failed" << std::endl;
return -1;
}
}
std::cout << "------------All success------------" << std::endl;
return 0;
}
@@ -146,10 +230,10 @@ bool update_items_data(const std::filesystem::path& input_dir, const std::filesy
"2023recruitment10",
"2024recruitment10",
"2025recruitment10",
"uni_set_" // 家具组合包
"uni_set_" // 家具组合包
};
static const std::vector<std::string> BlackListSuffix = {
"_rep_1", // 复刻活动的活动代币
"_rep_1", // 复刻活动的活动代币
};
bool is_blacklist = false;
@@ -324,15 +408,9 @@ bool update_infrast_data(const std::filesystem::path& input_dir, const std::file
// 为了兼容老版本的字段 orz
static const std::unordered_map<std::string, std::string> RoomTypeMapping = {
{ "POWER", "Power" },
{ "CONTROL", "Control" },
{ "DORMITORY", "Dorm" },
{ "WORKSHOP", "" },
{ "MANUFACTURE", "Mfg" },
{ "TRADING", "Trade" },
{ "MEETING", "Reception" },
{ "HIRE", "Office" },
{ "TRAINING", "" },
{ "POWER", "Power" }, { "CONTROL", "Control" }, { "DORMITORY", "Dorm" },
{ "WORKSHOP", "" }, { "MANUFACTURE", "Mfg" }, { "TRADING", "Trade" },
{ "MEETING", "Reception" }, { "HIRE", "Office" }, { "TRAINING", "" },
};
std::string room_type = RoomTypeMapping.at(raw_room_type);
@@ -412,12 +490,7 @@ bool update_infrast_templates(const std::filesystem::path& input_dir, const std:
}
const std::string& stem = entry.path().stem().string();
const std::vector<std::string> BlackList = {
"[style]",
"bskill_dorm",
"bskill_train",
"bskill_ws"
};
const std::vector<std::string> BlackList = { "[style]", "bskill_dorm", "bskill_train", "bskill_ws" };
bool is_blacklist = false;
for (const auto& bl : BlackList) {
@@ -456,11 +529,14 @@ bool update_infrast_templates(const std::filesystem::path& input_dir, const std:
return true;
}
bool update_roguelike_recruit(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir, const std::filesystem::path& solution_dir)
bool update_roguelike_recruit(const std::filesystem::path& input_dir, const std::filesystem::path& output_dir,
const std::filesystem::path& solution_dir)
{
std::string python_cmd;
std::filesystem::path python_file = solution_dir / "tools" / "RoguelikeResourceUpdater" / "generate_roguelike_recruit.py";
python_cmd = "python " + python_file.string() + " --input=\"" + input_dir.string() + "\" --output=\"" + output_dir.string() + "\"";
std::filesystem::path python_file =
solution_dir / "tools" / "RoguelikeResourceUpdater" / "generate_roguelike_recruit.py";
python_cmd = "python " + python_file.string() + " --input=\"" + input_dir.string() + "\" --output=\"" +
output_dir.string() + "\"";
int python_ret = system(python_cmd.c_str());
if (python_ret != 0) {
return false;
@@ -468,7 +544,8 @@ bool update_roguelike_recruit(const std::filesystem::path& input_dir, const std:
return true;
}
bool generate_english_roguelike_stage_name_replacement(const std::filesystem::path& ch_file, const std::filesystem::path& en_file)
bool generate_english_roguelike_stage_name_replacement(const std::filesystem::path& ch_file,
const std::filesystem::path& en_file)
{
auto ch_opt = json::open(ch_file);
auto en_opt = json::open(en_file);
@@ -528,16 +605,16 @@ bool update_battle_chars_info(const std::filesystem::path& input_dir, const std:
json::value result;
auto& range = result["ranges"].as_object();
for (auto& [id, range_data] : range_json.as_object()) {
if (int direction = range_data["direction"].as_integer();
direction != 1) {
if (int direction = range_data["direction"].as_integer(); direction != 1) {
// 现在都是 1朝右的以后不知道会不会改加个warning真遇到再说
std::cout << "!!!Warning!!! range_id: " << id << " 's direction is " << std::to_string(direction) << std::endl;
std::cout << "!!!Warning!!! range_id: " << id << " 's direction is " << std::to_string(direction)
<< std::endl;
}
json::array points;
for (auto& grids : range_data["grids"].as_array()) {
int x = grids["col"].as_integer();
int y = grids["row"].as_integer();
points.emplace_back(json::array{ x, y });
points.emplace_back(json::array { x, y });
}
range.emplace(id, std::move(points));
}
@@ -550,17 +627,13 @@ bool update_battle_chars_info(const std::filesystem::path& input_dir, const std:
char_new_data["name"] = name;
if (name == "阿米娅") {
char_new_data["profession"] = "WARRIOR";
char_new_data["rangeId"] = json::array{
"1-1",
"1-1",
"1-1"
};
char_new_data["rangeId"] = json::array { "1-1", "1-1", "1-1" };
}
else {
char_new_data["profession"] = char_data["profession"];
const std::string& default_range = char_data.get("phases", 0, "rangeId", "0-1");
char_new_data["rangeId"] = json::array{
char_new_data["rangeId"] = json::array {
default_range,
char_data.get("phases", 1, "rangeId", default_range),
char_data.get("phases", 2, "rangeId", default_range),
@@ -577,3 +650,130 @@ bool update_battle_chars_info(const std::filesystem::path& input_dir, const std:
return true;
}
bool update_recruitment_data(const std::filesystem::path& input_dir, const std::filesystem::path& output, bool is_base)
{
const auto& recruitment_file = input_dir / "gacha_table.json";
const auto& operators_file = input_dir / "character_table.json";
auto recruitment_opt = json::open(recruitment_file);
auto operatros_opt = json::open(operators_file);
if (!recruitment_opt || !operatros_opt) {
std::cerr << "Failed to parse recruitment or operators file" << std::endl;
return false;
}
std::vector<std::string> chars_list;
std::string recruitment_details = recruitment_opt->at("recruitDetail").as_string();
remove_xml(recruitment_details);
auto splited = Split(ReplaceAll(recruitment_details, "\\n", ""), "");
bool is_useless = true;
for (const auto& s : splited) {
if (s.find("Lancet-2") != std::string::npos) {
is_useless = false;
}
if (is_useless) {
continue;
}
for (const auto& n : Split(Split(s, "\n")[0], "/")) {
std::string name = n;
trim(name);
chars_list.emplace_back(name);
}
}
struct RecruitmentInfo
{
int rarity = 0;
std::vector<std::string> tags;
};
static std::unordered_map</*id*/ std::string, RecruitmentInfo> base_chars_info;
std::unordered_map</*name*/ std::string, /*id*/ std::string> chars_id_list;
for (auto& [id, char_data] : operatros_opt->as_object()) {
if (is_base) {
RecruitmentInfo info;
info.rarity = char_data["rarity"].as_integer() + 1;
for (const auto& tag : char_data["tagList"].as_array()) {
info.tags.emplace_back(tag.as_string());
}
std::string position = char_data["position"].as_string();
if (position == "MELEE") {
info.tags.emplace_back("近战位");
}
else if (position == "RANGED") {
info.tags.emplace_back("远程位");
}
else {
continue;
}
if (info.rarity == 5) {
info.tags.emplace_back("资深干员");
}
else if (info.rarity == 6) {
info.tags.emplace_back("高级资深干员");
}
static const std::unordered_map<std::string, std::string> RoleMap = {
{ "CASTER", "术师干员" }, { "MEDIC", "医疗干员" }, { "PIONEER", "先锋干员" },
{ "SNIPER", "狙击干员" }, { "SPECIAL", "特种干员" }, { "SUPPORT", "辅助干员" },
{ "TANK", "重装干员" }, { "WARRIOR", "近卫干员" },
};
auto role_iter = RoleMap.find(char_data["profession"].as_string());
if (role_iter == RoleMap.cend()) {
continue;
}
info.tags.emplace_back(role_iter->second);
base_chars_info.insert_or_assign(id, std::move(info));
}
chars_id_list.insert_or_assign(char_data["name"].as_string(), id);
}
json::value result;
auto& opers = result["operators"];
for (const std::string& name : chars_list) {
auto id_iter = chars_id_list.find(name);
if (id_iter == chars_id_list.cend()) {
std::cerr << "Failed to find char: " << name << std::endl;
return false;
}
const std::string& id = id_iter->second;
auto info_iter = base_chars_info.find(id);
if (info_iter == base_chars_info.cend()) {
std::cerr << "Failed to find char's info: " << id << name << std::endl;
return false;
}
opers.array_emplace(json::object { { "id", id },
{ "name", name },
{ "rarity", info_iter->second.rarity },
{ "tags", json::array(info_iter->second.tags) } });
}
static std::unordered_map</*id*/ int, /*tag*/ std::string> base_tags_name;
std::unordered_map</*id*/ int, /*tag*/ std::string> tags_name;
for (const auto& tag_json : recruitment_opt->at("gachaTags").as_array()) {
int id = tag_json.at("tagId").as_integer();
std::string name = tag_json.at("tagName").as_string();
if (is_base) {
base_tags_name.insert_or_assign(id, name);
}
tags_name.insert_or_assign(id, name);
}
auto& tags = result["tags"];
for (const auto& [id, tag] : tags_name) {
std::string base_name = base_tags_name.at(id);
tags.object_emplace(base_name, tag);
}
std::ofstream ofs(output, std::ios::out);
ofs << result.format(true) << std::endl;
return true;
}