重构基建的配置文件及相关代码逻辑

This commit is contained in:
MistEO
2021-08-19 01:30:23 +08:00
parent c1f0feca45
commit 3e0e7e29ec
11 changed files with 443 additions and 334 deletions

View File

@@ -42,33 +42,39 @@ bool InfrastConfiger::_load(const std::string& filename)
json::value root = ret.value();
try {
// ÖÆÔìÕ¾
for (json::value& comb_info : root["Manufacturing"]["combs"].as_array())
// 通用的干员信息
for (json::value& name : root["allNames"].as_array())
{
std::vector<std::string> comb_vec;
for (json::value& oper_name : comb_info["comb"].as_array())
{
std::string oper_name_str = oper_name.as_string();
comb_vec.emplace_back(std::move(oper_name_str));
}
m_mfg_combs.emplace_back(std::move(comb_vec));
m_all_opers_name.emplace(name.as_string());
}
m_mfg_end = root["Manufacturing"]["end"].as_string();
for (json::value& oper : root["Manufacturing"]["all"].as_array())
for (json::value& pair : root["featureKey"].as_array())
{
m_mfg_opers.emplace(oper["name"].as_string());
m_oper_name_feat.emplace(pair[0].as_string(), pair[1].as_string());
}
for (json::value& pair : root["Manufacturing"]["featureKey"].as_array())
for (json::value& name : root["featureWhatever"].as_array())
{
m_mfg_feat.emplace(pair[0].as_string(), pair[1].as_string());
m_oper_name_feat_whatever.emplace(name.as_string());
}
for (json::value& name : root["Manufacturing"]["featureWhatever"].as_array())
{
m_mfg_feat_whatever.emplace(name.as_string());
}
// óÒ×Õ¾ TODO¡­¡­
// 每个基建设施中的干员组合信息
for (json::value& facility : root["infrast"].as_array())
{
std::string key = facility["facility"].as_string();
std::vector< std::vector<OperInfrastInfo>> combs_vec;
for (json::value& comb : facility["combs"].as_array())
{
std::vector<OperInfrastInfo> opers;
for (json::value& oper : comb["opers"].as_array()) {
OperInfrastInfo info;
info.name = oper["name"].as_string();
info.elite = oper.get("elite", 0);
info.level = oper.get("level", 0);
opers.emplace_back(std::move(info));
}
combs_vec.emplace_back(std::move(opers));
}
m_infrast_combs.emplace(std::move(key), std::move(combs_vec));
}
}
catch (json::exception& e) {
DebugTraceError("Load config json error!", e.what());

View File

@@ -70,7 +70,7 @@ namespace asst {
{
int half_width_scale = static_cast<int>(width * (1 - scale) / 2);
int half_hight_scale = static_cast<int>(height * (1 - scale) / 2);
return { x + half_width_scale, y + half_hight_scale,
return { x + half_width_scale, y + half_hight_scale,
static_cast<int>(width * scale), static_cast<int>(height * scale) };
}
int x = 0;
@@ -177,8 +177,8 @@ namespace asst {
int ocr_thread_number = 0; // OcrLite线程数量
};
// 干员信息
struct OperInfo {
// 干员信息,公开招募相关
struct OperRecruitInfo {
std::string name;
std::string type;
int level = 0;
@@ -188,13 +188,34 @@ namespace asst {
std::string name_en;
};
// 干员组合
struct OperCombs {
std::vector<OperInfo> opers;
// 公开招募的干员组合
struct OperRecruitCombs {
std::vector<OperRecruitInfo> opers;
int max_level = 0;
int min_level = 0;
double avg_level = 0;
};
// 干员信息,基建相关
struct OperInfrastInfo {
std::string name;
int elite = 0; // 精英化等级
int level = 0; // 等级
bool operator==(const OperInfrastInfo& rhs) const {
return name == rhs.name;
}
};
constexpr double DoubleDiff = 1e-12;
}
namespace std {
template<>
class hash<asst::OperInfrastInfo> {
public:
size_t operator()(const asst::OperInfrastInfo& info) const
{
return std::hash<std::string>()(info.name);
}
};
}

View File

@@ -18,12 +18,11 @@ namespace asst {
}
bool load(const std::string& filename);
std::unordered_set<std::string> m_all_opers_name; // 所有干员的名字
std::unordered_map<std::string, std::string> m_oper_name_feat; // 根据关键字需要特征检测干员名如果OCR识别到了key的内容但是却没有value的内容则进行特征检测进一步确认
std::unordered_set<std::string> m_oper_name_feat_whatever; // 无论如何都进行特征检测的干员名
std::vector<std::vector<std::string>> m_mfg_combs; // 制造站组合
std::unordered_set<std::string> m_mfg_opers; // 制造站所有干员
std::unordered_map<std::string, std::string> m_mfg_feat; // 特征检测关键字如果OCR识别到了key的内容但是却没有value的内容则进行特征检测进一步确认
std::unordered_set<std::string> m_mfg_feat_whatever; // 无论如何都进行特征检测的干员名
std::string m_mfg_end; // 识别到这个词,就认为干员遍历结束,一般用排序里最后的那个干员(?)
std::unordered_map<std::string, std::vector<std::vector<OperInfrastInfo>>> m_infrast_combs; // 各个设施内的可能干员组合
private:
InfrastConfiger() = default;

View File

@@ -22,7 +22,7 @@ namespace asst {
std::unordered_set<std::string> m_all_tags;
std::unordered_set<std::string> m_all_types;
std::vector<OperInfo> m_all_opers;
std::vector<OperRecruitInfo> m_all_opers;
constexpr static int CorrectNumberOfTags = 5;

View File

@@ -272,12 +272,12 @@ namespace asst {
m_swipe_end = end_rect;
m_swipe_max_times = max_times;
}
void set_facility(FacilityType facility)
void set_facility(const std::string& facility)
{
m_facility = facility;
}
private:
FacilityType m_facility = FacilityType::Invalid;
std::string m_facility;
int m_swipe_delay = 0;
Rect m_swipe_begin;
Rect m_swipe_end;

View File

@@ -68,14 +68,14 @@ Assistance::Assistance(AsstCallback callback, void* callback_arg)
return;
}
for (const auto& [key, name] : InfrastConfiger::get_instance().m_mfg_feat) {
for (const auto& [key, name] : InfrastConfiger::get_instance().m_oper_name_feat) {
ret = m_identify_ptr->add_text_image(name, GetResourceDir() + "operators\\" + Utf8ToGbk(name) + ".png");
if (!ret) {
callback_error();
return;
}
}
for (const auto& name : InfrastConfiger::get_instance().m_mfg_feat_whatever) {
for (const auto& name : InfrastConfiger::get_instance().m_oper_name_feat_whatever) {
ret = m_identify_ptr->add_text_image(name, GetResourceDir() + "operators\\" + Utf8ToGbk(name) + ".png");
if (!ret) {
callback_error();
@@ -255,8 +255,8 @@ bool asst::Assistance::start_infrast()
auto task_ptr = std::make_shared<InfrastStationTask>(task_callback, (void*)this);
// TODO 这个参数写到配置文件里TODO 滑动位置要根据分辨率缩放
task_ptr->set_swipe_param(1000, Rect(1800, 800, 0, 0), Rect(1200, 800, 0, 0), 20);
task_ptr->set_facility(FacilityType::Manufacturing);
task_ptr->set_swipe_param(2100, Rect(2400, 800, 0, 0), Rect(400, 800, 0, 0), 20);
task_ptr->set_facility("Manufacturing");
task_ptr->set_task_chain("Infrast");
m_tasks_queue.emplace(task_ptr);

View File

@@ -51,7 +51,7 @@ bool asst::RecruitConfiger::_load(const std::string& filename)
json::value root = ret.value();
try {
for (json::value& oper : root.as_array()) {
OperInfo oper_temp;
OperRecruitInfo oper_temp;
oper_temp.name = oper["name"].as_string();
oper_temp.type = oper["type"].as_string();
m_all_types.emplace(oper_temp.type);

View File

@@ -517,10 +517,10 @@ bool OpenRecruitTask::run()
}
// key: tags comb, value: 干员组合
// 例如 key: { "狙击"、"群攻" }value: OperCombs.opers{ "陨星", "白雪", "空爆" }
std::map<std::vector<std::string>, OperCombs> result_map;
// 例如 key: { "狙击"、"群攻" }value: OperRecruitCombs.opers{ "陨星", "白雪", "空爆" }
std::map<std::vector<std::string>, OperRecruitCombs> result_map;
for (const std::vector<std::string>& comb : all_combs) {
for (const OperInfo& cur_oper : RecruitConfiger::get_instance().m_all_opers) {
for (const OperRecruitInfo& cur_oper : RecruitConfiger::get_instance().m_all_opers) {
int matched_count = 0;
for (const std::string& tag : comb) {
if (cur_oper.tags.find(tag) != cur_oper.tags.cend()) {
@@ -544,7 +544,7 @@ bool OpenRecruitTask::run()
}
}
OperCombs& oper_combs = result_map[comb];
OperRecruitCombs& oper_combs = result_map[comb];
oper_combs.opers.emplace_back(cur_oper);
if (cur_oper.level == 1 || cur_oper.level == 2) {
@@ -562,13 +562,13 @@ bool OpenRecruitTask::run()
oper_combs.avg_level += cur_oper.level;
}
if (result_map.find(comb) != result_map.cend()) {
OperCombs& oper_combs = result_map[comb];
OperRecruitCombs& oper_combs = result_map[comb];
oper_combs.avg_level /= oper_combs.opers.size();
}
}
// map没法按值排序转个vector再排序
std::vector<std::pair<std::vector<std::string>, OperCombs>> result_vector;
std::vector<std::pair<std::vector<std::string>, OperRecruitCombs>> result_vector;
for (auto&& pair : result_map) {
result_vector.emplace_back(std::move(pair));
}
@@ -604,7 +604,7 @@ bool OpenRecruitTask::run()
comb_json["tags"] = json::array(std::move(tags_json_vector));
std::vector<json::value> opers_json_vector;
for (const OperInfo& oper_info : oper_comb.opers) {
for (const OperRecruitInfo& oper_info : oper_comb.opers) {
json::value oper_json;
oper_json["name"] = Utf8ToGbk(oper_info.name);
oper_json["level"] = oper_info.level;
@@ -685,27 +685,12 @@ bool TestOcrTask::run()
return false;
}
std::vector<std::vector<std::string>> all_oper_combs; // 所有的干员组合
std::unordered_set<std::string> all_oper_name; // 所有干员名
std::string oper_end_flag; // 干员名结束标记识别到这个string就认为识别完成了
std::unordered_map<std::string, std::string> feature_cond_default; // 特征检测关键字如果OCR识别到了key的内容但是却没有value的内容则进行特征检测进一步确认
std::unordered_set<std::string> feature_whatever_default; // 无论如何都进行特征检测的
all_oper_combs = InfrastConfiger::get_instance().m_mfg_combs;
all_oper_name = InfrastConfiger::get_instance().m_mfg_opers;
oper_end_flag = InfrastConfiger::get_instance().m_mfg_end;
feature_cond_default = InfrastConfiger::get_instance().m_mfg_feat;
feature_whatever_default = InfrastConfiger::get_instance().m_mfg_feat_whatever;
std::unordered_map<std::string, std::string> feature_cond = feature_cond_default;
std::unordered_set<std::string> feature_whatever = feature_whatever_default;
auto detect_foo = [&](const cv::Mat& image) -> std::vector<TextArea> {
std::vector<TextArea> all_text_area = ocr_detect(image);
/* 过滤出所有制造站中的干员名 */
std::vector<TextArea> cur_name_textarea = text_search(
all_text_area,
all_oper_name,
InfrastConfiger::get_instance().m_all_opers_name,
Configer::get_instance().m_infrast_ocr_replace);
return cur_name_textarea;
};
@@ -721,12 +706,20 @@ bool TestOcrTask::run()
cv::Rect elite_rect;
elite_rect.x = textarea.rect.x - 200;
elite_rect.y = textarea.rect.y - 200;
elite_rect.width = 200;
elite_rect.width = 100;
elite_rect.height = 150;
auto&& [socre1, point1] = m_identify_ptr->match_template(image(elite_rect), elite1);
auto&& [socre2, point2] = m_identify_ptr->match_template(image(elite_rect), elite2);
auto&& [score1, point1] = m_identify_ptr->match_template(image(elite_rect), elite1);
auto&& [score2, point2] = m_identify_ptr->match_template(image(elite_rect), elite2);
std::cout << Utf8ToGbk(textarea.text) << " elite1: " << socre1 << ", eilte2: " << socre2 << std::endl;
if (score1 > score2 && score1 > 0.7) {
std::cout << Utf8ToGbk(textarea.text) << ",精一, elite1: " << score1 << ", eilte2: " << score2 << std::endl;
}
else if (score2 > score1 && score2 > 0.7) {
std::cout << Utf8ToGbk(textarea.text) << ",精二, elite1: " << score1 << ", eilte2: " << score2 << std::endl;
}
else {
std::cout << Utf8ToGbk(textarea.text) << ",未精英化, elite1: " << score1 << ", eilte2: " << score2 << std::endl;
}
//m_identify_ptr->feature_match(image(elite_rect), "Elite1");
//m_identify_ptr->feature_match(image(elite_rect), "Elite2");
@@ -750,24 +743,9 @@ bool asst::InfrastStationTask::run()
}
std::vector<std::vector<std::string>> all_oper_combs; // 所有的干员组合
std::unordered_set<std::string> all_oper_name; // 所有干员名
std::string oper_end_flag; // 干员名结束标记识别到这个string就认为识别完成了
std::unordered_map<std::string, std::string> feature_cond_default; // 特征检测关键字如果OCR识别到了key的内容但是却没有value的内容则进行特征检测进一步确认
std::unordered_set<std::string> feature_whatever_default; // 无论如何都进行特征检测的
switch (m_facility) {
case FacilityType::Manufacturing:
all_oper_combs = InfrastConfiger::get_instance().m_mfg_combs;
all_oper_name = InfrastConfiger::get_instance().m_mfg_opers;
oper_end_flag = InfrastConfiger::get_instance().m_mfg_end;
feature_cond_default = InfrastConfiger::get_instance().m_mfg_feat;
feature_whatever_default = InfrastConfiger::get_instance().m_mfg_feat_whatever;
break;
// TODO 贸易站和其他啥的,有空再做
default:
break;
}
json::value task_start_json = json::object{
{ "task_type", "InfrastStationTask" },
{ "task_chain", m_task_chain},
@@ -793,7 +771,7 @@ bool asst::InfrastStationTask::run()
/* 过滤出所有制造站中的干员名 */
std::vector<TextArea> cur_name_textarea = text_search(
all_text_area,
all_oper_name,
InfrastConfiger::get_instance().m_all_opers_name,
Configer::get_instance().m_infrast_ocr_replace);
// 用特征检测再筛选一遍OCR识别漏了的
@@ -836,14 +814,9 @@ bool asst::InfrastStationTask::run()
return cur_name_textarea;
};
// 识别到的干员名
std::unordered_set<std::string> detected_names;
json::value opers_json;
std::unordered_set<std::string> detected_names_gbk; // 给回调json用的
std::unordered_set<OperInfrastInfo> detected_opers;
// 一边识别一边滑动,把所有制造站干员名字抓出来
for (int i = 0; i != m_swipe_max_times; ++i) {
while (true) {
const cv::Mat& image = get_format_image(true);
// 异步进行滑动操作
@@ -851,85 +824,115 @@ bool asst::InfrastStationTask::run()
auto cur_name_textarea = detect_foo(image);
std::unordered_set<std::string> new_names_set;
for (TextArea& text_area : cur_name_textarea) {
new_names_set.emplace(Utf8ToGbk(text_area.text));
detected_names_gbk.emplace(Utf8ToGbk(text_area.text));
detected_names.emplace(std::move(text_area.text));
}
bool break_flag = false;
// 识别到了结束标记,就直接退出循环
if (detected_names.find(oper_end_flag) != detected_names.cend()) {
break_flag = true;
}
int oper_numer = detected_opers.size();
for (const TextArea& textarea : cur_name_textarea)
{
cv::Rect elite_rect;
// 因为有的名字长有的名字短,但是右对齐的,所以跟着右边走
elite_rect.x = textarea.rect.x + textarea.rect.width - 250;
elite_rect.y = textarea.rect.y - 200;
if (elite_rect.x < 0 || elite_rect.y < 0) {
continue;
}
elite_rect.width = 100;
elite_rect.height = 150;
cv::Mat elite_mat = image(elite_rect);
opers_json["all"] = json::array(detected_names_gbk);
opers_json["new"] = json::array(new_names_set);
// for debug
static cv::Mat elite1 = cv::imread(GetResourceDir() + "operators\\Elite1.png");
static cv::Mat elite2 = cv::imread(GetResourceDir() + "operators\\Elite2.png");
auto&& [score1, point1] = m_identify_ptr->match_template(elite_mat, elite1);
auto&& [score2, point2] = m_identify_ptr->match_template(elite_mat, elite2);
std::cout << "elite1:" << score1 << ", elite2:" << score2 << std::endl;
OperInfrastInfo info;
info.name = textarea.text;
if (score1 > score2 && score1 > 0.7) {
info.elite = 1;
}
else if (score2 > score1 && score2 > 0.7) {
info.elite = 2;
}
else {
info.elite = 0;
}
detected_opers.emplace(std::move(info));
}
json::value opers_json;
std::vector<json::value> opers_json_vec;
for (const OperInfrastInfo& info : detected_opers) {
json::value info_json;
info_json["name"] = Utf8ToGbk(info.name);
info_json["elite"] = info.elite;
//info_json["level"] = info.level;
opers_json_vec.emplace_back(std::move(info_json));
}
opers_json["all"] = json::array(opers_json_vec);
m_callback(AsstMsg::InfrastOpers, opers_json, m_callback_arg);
// 阻塞等待滑动结束
if (!swipe_future.get()) {
return false;
}
if (break_flag) {
break;
}
}
// 配置文件中的干员组合,和抓出来的干员名比对,如果组合中的干员都有,那就用这个组合
// Todo 时间复杂度起飞了,需要优化下
std::vector<std::string> optimal_comb;
for (auto&& name_vec : all_oper_combs) {
int count = 0;
for (std::string& name : name_vec) {
if (detected_names.find(name) != detected_names.cend()) {
++count;
}
else {
break;
}
}
if (count == name_vec.size()) {
optimal_comb = name_vec;
// 说明本次识别一个新的都没识别到,应该是滑动到最后了,直接结束循环
if (oper_numer == detected_opers.size()) {
break;
}
}
std::vector<std::string> optimal_comb_gbk; // 给回调json用的gbk的
for (const std::string& name : optimal_comb)
{
optimal_comb_gbk.emplace_back(Utf8ToGbk(name));
}
//// 配置文件中的干员组合,和抓出来的干员名比对,如果组合中的干员都有,那就用这个组合
//// Todo 时间复杂度起飞了,需要优化下
//std::vector<std::string> optimal_comb;
//for (auto&& name_vec : all_oper_combs) {
// int count = 0;
// for (std::string& name : name_vec) {
// if (detected_names.find(name) != detected_names.cend()) {
// ++count;
// }
// else {
// break;
// }
// }
// if (count == name_vec.size()) {
// optimal_comb = name_vec;
// break;
// }
//}
opers_json["comb"] = json::array(optimal_comb_gbk);
m_callback(AsstMsg::InfrastComb, opers_json, m_callback_arg);
//std::vector<std::string> optimal_comb_gbk; // 给回调json用的gbk的
//for (const std::string& name : optimal_comb)
//{
// optimal_comb_gbk.emplace_back(Utf8ToGbk(name));
//}
// 重置特征检测的条件后面不用了这次直接move
feature_cond = std::move(feature_cond_default);
feature_whatever = std::move(feature_whatever_default);
//opers_json["comb"] = json::array(optimal_comb_gbk);
//m_callback(AsstMsg::InfrastComb, opers_json, m_callback_arg);
// 一边滑动一边点击最优解中的干员
for (int i = 0; i != m_swipe_max_times; ++i) {
const cv::Mat& image = get_format_image(true);
auto cur_name_textarea = detect_foo(image);
//// 重置特征检测的条件后面不用了这次直接move
//feature_cond = std::move(feature_cond_default);
//feature_whatever = std::move(feature_whatever_default);
for (TextArea& text_area : cur_name_textarea) {
// 点过了就不会再点了直接从最优解vector里面删了
auto iter = std::find(optimal_comb.begin(), optimal_comb.end(), text_area.text);
if (iter != optimal_comb.end()) {
m_control_ptr->click(text_area.rect);
optimal_comb.erase(iter);
}
}
if (optimal_comb.empty()) {
break;
}
// 因为滑动和点击是矛盾的,这里没法异步做
if (!swipe_foo(true)) {
return false;
}
}
//// 一边滑动一边点击最优解中的干员
//for (int i = 0; i != m_swipe_max_times; ++i) {
// const cv::Mat& image = get_format_image(true);
// auto cur_name_textarea = detect_foo(image);
// for (TextArea& text_area : cur_name_textarea) {
// // 点过了就不会再点了直接从最优解vector里面删了
// auto iter = std::find(optimal_comb.begin(), optimal_comb.end(), text_area.text);
// if (iter != optimal_comb.end()) {
// m_control_ptr->click(text_area.rect);
// optimal_comb.erase(iter);
// }
// }
// if (optimal_comb.empty()) {
// break;
// }
// // 因为滑动和点击是矛盾的,这里没法异步做
// if (!swipe_foo(true)) {
// return false;
// }
//}
return true;
}

View File

@@ -24,7 +24,7 @@ int main(int argc, char** argv)
char ch = 0;
while (ch != 'q') {
test_ocr(ptr);
test_infrast(ptr);
//test_swipe(ptr);
ch = getchar();
@@ -40,7 +40,7 @@ int main(int argc, char** argv)
void test_swipe(Assistance* ptr)
{
AsstTestSwipe(ptr, 1000, 300, 500, 300);
AsstTestSwipe(ptr, 2500, 300, 100, 300);
}
void test_ocr(Assistance* ptr)

View File

@@ -58,7 +58,7 @@
"path": "[EmulatorPath]Engine\\ProgramFiles\\HD-Adb.exe",
"connect": "[Adb] connect 127.0.0.1:5555",
"click": "[Adb] -s 127.0.0.1:5555 shell input tap [x] [y]",
"swipe": "[Adb] -s 127.0.0.1:5555 shell input swipe [x1] [y1] [x2] [y2]",
"swipe": "[Adb] -s 127.0.0.1:5555 shell input swipe [x1] [y1] [x2] [y2] 2000",
"display": "[Adb] -s 127.0.0.1:5555 shell dumpsys window displays | grep init= | awk ' { print $3 } '",
"displayRegex": "cur=%dx%d",
"screencap": "[Adb] -s 127.0.0.1:5555 shell screencap /sdcard/meo_screen.png",
@@ -92,7 +92,7 @@
"path": "[EmulatorPath]..\\vmonitor\\bin\\adb_server.exe",
"connect": "[Adb] connect 127.0.0.1:7555",
"click": "[Adb] -s 127.0.0.1:7555 shell input tap [x] [y]",
"swipe": "[Adb] -s 127.0.0.1:7555 shell input swipe [x1] [y1] [x2] [y2]",
"swipe": "[Adb] -s 127.0.0.1:7555 shell input swipe [x1] [y1] [x2] [y2] 2000",
"display": "[Adb] -s 127.0.0.1:7555 shell dumpsys window displays | grep init= | awk ' { print $3 } '",
"displayRegex": "cur=%dx%d",
"screencap": "[Adb] -s 127.0.0.1:7555 shell screencap /sdcard/meo_screen.png",

View File

@@ -1,178 +1,258 @@
{
"Manufacturing": {
"Doc": "制造站",
"combs": [
{
"comb": [
"刻俄柏",
"稀音",
"红云"
],
"efficiency": 105
},
{
"comb": [
"温蒂",
"异客",
"森蚺"
],
"efficiency": 90
},
{
"comb": [
"月见夜"
],
"efficiency": 10
}
],
"end": "月见夜",
"end_Doc": "识别到这个词,就认为干员遍历结束,一般用排序里最后的那个干员(?)",
"featureKey_Doc": "特征检测的关键字如果OCR识别到了左边的内容但是却没有右边的内容则进行特征检测进一步确认",
"featureKey": [
[ "森", "森蚺" ],
[ "白面", "白面鸮"]
],
"featureWhatever_Doc": "这里面的无论如何都进行特征检测",
"featureWhatever": [ "砾" ],
"all": [
{
"name": "食铁兽"
},
{
"name": "断罪者"
},
{
"name": "白雪"
},
{
"name": "Castle-3"
},
{
"name": "霜叶"
},
{
"name": "红豆"
},
{
"name": "帕拉斯"
},
{
"name": "砾"
},
{
"name": "夜烟"
},
{
"name": "斑点"
},
{
"name": "清流"
},
{
"name": "迷迭香"
},
{
"name": "槐琥"
},
{
"name": "梅尔"
},
{
"name": "赫默"
},
{
"name": "白面鸮"
},
{
"name": "水月"
},
{
"name": "调香师"
},
{
"name": "杰西卡"
},
{
"name": "史都华德"
},
{
"name": "香草"
},
{
"name": "石棉"
},
{
"name": "泡普卡"
},
{
"name": "芬"
},
{
"name": "克洛丝"
},
{
"name": "豆苗"
},
{
"name": "流星"
},
{
"name": "夜刀"
},
{
"name": "温蒂"
},
{
"name": "森蚺"
},
{
"name": "异客"
},
{
"name": "蛇屠箱"
},
{
"name": "黑角"
},
{
"name": "卡缇"
},
{
"name": "米格鲁"
},
{
"name": "火神"
},
{
"name": "贝娜"
},
{
"name": "卡达"
},
{
"name": "稀音"
},
{
"name": "泡泡"
},
{
"name": "刻俄柏"
},
{
"name": "红云"
},
{
"name": "艾雅法拉"
},
{
"name": "炎熔"
},
{
"name": "月见夜"
}
]
},
"Trade": {
"Doc": "贸易站"
}
"infrast": [
{
"facility": "Manufacturing",
"Doc": "制造站",
"combs": [
{
"opers": [
{
"name": "刻俄柏",
"elite": 2
},
{
"name": "稀音",
"elite": 2
},
{
"name": "红云",
"elite": 1
}
],
"efficiency": 105
},
{
"opers": [
{
"name": "温蒂",
"elite": 2
},
{
"name": "异客",
"elite": 2
},
{
"name": "森蚺",
"elite": 2
}
],
"efficiency": 90
}
]
},
{
"facility": "Trade",
"Doc": "贸易站",
"combs": [
]
}
],
"featureKey_Doc": "特征检测的关键字如果OCR识别到了左边的内容但是却没有右边的内容则进行特征检测进一步确认",
"featureKey": [
[ "森", "森蚺" ],
[ "白面", "白面鸮" ]
],
"featureWhatever_Doc": "这里面的无论如何都进行特征检测",
"featureWhatever": [ "砾" ],
"allNames": [
"食铁兽",
"断罪者",
"白雪",
"Castle-3",
"霜叶",
"红豆",
"帕拉斯",
"砾",
"夜烟",
"斑点",
"清流",
"迷迭香",
"槐琥",
"梅尔",
"赫默",
"白面鸮",
"水月",
"调香师",
"杰西卡",
"史都华德",
"香草",
"石棉",
"泡普卡",
"芬",
"克洛丝",
"豆苗",
"流星",
"夜刀",
"温蒂",
"森蚺",
"异客",
"蛇屠箱",
"黑角",
"卡缇",
"米格鲁",
"火神",
"贝娜",
"卡达",
"稀音",
"泡泡",
"刻俄柏",
"红云",
"艾雅法拉",
"炎熔",
"月见夜",
"安洁莉娜",
"嵯峨",
"风笛",
"赫拉格",
"黑",
"煌",
"棘刺",
"卡涅利安",
"傀影",
"铃兰",
"泥岩",
"塞雷娅",
"山",
"史尔特尔",
"闪灵",
"斯卡蒂",
"推进之王",
"星熊",
"银灰",
"浊心斯卡蒂",
"W",
"歌蕾蒂娅",
"假日威龙陈",
"空弦",
"麦哲伦",
"莫斯提马",
"年",
"早露",
"阿",
"夕",
"瑕光",
"阿米娅",
"幽灵鲨",
"白金",
"柏喙",
"鞭刃",
"华法琳",
"极境",
"贾维",
"空",
"临光",
"狮蝎",
"星极",
"陨星",
"真理",
"初雪",
"格劳克斯",
"凛冬",
"送葬人",
"苇草",
"爱丽丝",
"蓝毒",
"天火",
"锡兰",
"罗宾",
"特米米",
"图耶",
"雪雉",
"炎狱炎熔",
"羽毛笔",
"嘉维尔",
"末药",
"安比尔",
"暗索",
"缠丸",
"古米",
"坚雷",
"孑",
"刻刀",
"猎蜂",
"梅",
"苏苏洛",
"讯使",
"宴",
"远山",
"玫兰莎",
"空爆",
"安德切尔",
"安塞尔",
"芙蓉",
"翎羽",
"梓兰",
"安哲拉",
"奥斯塔",
"拜松",
"暴行",
"暴雨",
"薄绿",
"布洛卡",
"赤东",
"断崖",
"芙兰卡",
"格拉尼",
"吽",
"灰喉",
"惊蛰",
"可颂",
"苦艾",
"莱因哈特",
"蜜蜡",
"普罗旺斯",
"绮良",
"熔泉",
"慑砂",
"诗怀雅",
"守林人",
"四月",
"燧石",
"微风",
"乌有",
"絮雨",
"崖心",
"亚叶",
"炎客",
"夜魔",
"月禾",
"铸铁",
"艾丝戴尔",
"波登可",
"地灵",
"杜宾",
"芳汀",
"角峰",
"杰克",
"慕斯",
"清道夫",
"深靛",
"深海色",
"松果",
"酸糖",
"12F",
"杜林",
"巡林者",
"Lancet-2",
"闪击",
"战车",
"红",
"卡夫卡",
"龙舌兰",
"巫恋",
"阿消",
"雷蛇",
"伊桑",
"陈",
"灰烬",
"凯尔希",
"霜华",
"拉普兰德",
"格雷伊",
"能天使",
"德克萨斯",
"因陀罗",
"伊芙利特",
"THRM-EX"
]
}