Files
MaaAssistantArknights/src/MaaCore/Task/Miscellaneous/AutoRecruitTask.cpp
2024-02-24 22:25:19 +08:00

853 lines
30 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "AutoRecruitTask.h"
#include "Config/GeneralConfig.h"
#include "Config/Miscellaneous/RecruitConfig.h"
#include "Config/TaskData.h"
#include "Controller/Controller.h"
#include "Task/ProcessTask.h"
#include "Task/ReportDataTask.h"
#include "Utils/Logger.hpp"
#include "Vision/Miscellaneous/RecruitImageAnalyzer.h"
#include "Vision/MultiMatcher.h"
#include "Vision/OCRer.h"
#include "Utils/Ranges.hpp"
#include <algorithm>
#include <regex>
namespace asst::recruit_calc
{
// all combinations and their operator list, excluding empty set and 6-star operators while there is no senior tag
auto get_all_combs(const std::vector<RecruitConfig::TagId>& tags,
const std::vector<Recruitment>& all_ops = RecruitData.get_all_opers())
{
std::vector<RecruitCombs> rcs_with_single_tag;
{
rcs_with_single_tag.reserve(tags.size());
ranges::transform(tags, std::back_inserter(rcs_with_single_tag), [](const RecruitConfig::TagId& t) {
RecruitCombs result;
result.tags = { t };
result.min_level = 6;
result.max_level = 0;
result.avg_level = 0;
return result;
});
for (const auto& op : all_ops) {
for (auto& rc : rcs_with_single_tag) {
if (!op.has_tag(rc.tags.front())) continue;
rc.opers.push_back(op);
rc.min_level = (std::min)(rc.min_level, op.level);
rc.max_level = (std::max)(rc.max_level, op.level);
rc.avg_level += op.level;
}
}
for (auto& rc : rcs_with_single_tag) {
rc.avg_level /= static_cast<double>(rc.opers.size());
// intersection and union are based on sorted container
ranges::sort(rc.tags);
ranges::sort(rc.opers);
}
}
std::vector<RecruitCombs> result;
const size_t tag_size = tags.size();
result.reserve(tag_size * (tag_size * tag_size + 5) / 6); // C(size, 3) + C(size, 2) + C(size, 1)
// select one tag first
for (size_t i = 0; i < tag_size; ++i) {
RecruitCombs temp1 = rcs_with_single_tag[i];
if (temp1.opers.empty()) [[unlikely]] {
continue; // this is not possible
}
result.emplace_back(temp1); // that is it
// but what if another tag is also selected
for (size_t j = i + 1; j < tag_size; ++j) {
RecruitCombs temp2 = temp1 * rcs_with_single_tag[j];
if (temp2.opers.empty()) [[unlikely]] {
continue;
}
result.emplace_back(temp2); // two tags only
// select a third one
for (size_t k = j + 1; k < tag_size; ++k) {
RecruitCombs temp3 = temp2 * rcs_with_single_tag[k];
if (temp3.opers.empty()) [[unlikely]] {
continue;
}
result.emplace_back(temp3);
}
}
}
static constexpr std::string_view SeniorOper = "高级资深干员";
for (auto comb_iter = result.begin(); comb_iter != result.end();) {
if (ranges::find(comb_iter->tags, RecruitConfig::TagId(SeniorOper)) != comb_iter->tags.end()) {
++comb_iter;
continue;
}
// no senior tag, remove 6-star operators
// assuming sorted by level
auto iter = ranges::find_if(comb_iter->opers, [](const Recruitment& op) { return op.level >= 6; });
if (iter == comb_iter->opers.end()) {
++comb_iter;
continue;
}
comb_iter->opers.erase(iter, comb_iter->opers.end());
if (comb_iter->opers.empty()) {
comb_iter = result.erase(comb_iter);
continue;
}
comb_iter->update_attributes();
++comb_iter;
}
return result;
}
} // namespace asst::recruit_calc
asst::AutoRecruitTask& asst::AutoRecruitTask::set_select_level(std::vector<int> select_level) noexcept
{
m_select_level = std::move(select_level);
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_confirm_level(std::vector<int> confirm_level) noexcept
{
m_confirm_level = std::move(confirm_level);
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_need_refresh(bool need_refresh) noexcept
{
m_need_refresh = need_refresh;
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_max_times(int max_times) noexcept
{
m_max_times = max_times;
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_use_expedited(bool use_or_not) noexcept
{
m_use_expedited = use_or_not;
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_select_extra_tags(ExtraTagsMode select_extra_tags_mode) noexcept
{
m_select_extra_tags_mode = select_extra_tags_mode;
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_skip_robot(bool skip_robot) noexcept
{
m_skip_robot = skip_robot;
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_set_time(bool set_time) noexcept
{
m_set_time = set_time;
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_force_refresh(bool force_refresh) noexcept
{
m_force_refresh = force_refresh;
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_recruitment_time(std::unordered_map<int, int> time_map) noexcept
{
m_desired_time_map = std::move(time_map);
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_penguin_enabled(bool enable, std::string penguin_id) noexcept
{
m_upload_to_penguin = enable;
if (!penguin_id.empty()) {
m_penguin_id = std::move(penguin_id);
}
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_yituliu_enabled(bool enable, std::string yituliu_id) noexcept
{
m_upload_to_yituliu = enable;
if (!yituliu_id.empty()) {
m_yituliu_id = std::move(yituliu_id);
}
return *this;
}
asst::AutoRecruitTask& asst::AutoRecruitTask::set_server(std::string server) noexcept
{
m_server = std::move(server);
return *this;
}
bool asst::AutoRecruitTask::_run()
{
if (is_calc_only_task()) {
return recruit_calc_task().success;
}
if (!recruit_begin()) return false;
{
const auto image = ctrler()->get_image();
// initialize_dirty_slot_info(image);
m_dirty_slots = { 0, 1, 2, 3 };
if (!hire_all(image)) return false;
}
static constexpr int slot_retry_limit = 3;
bool try_use_expedited = m_use_expedited;
// m_cur_times means how many times has the confirm button been pressed, NOT expedited plans used
while (m_cur_times != m_max_times) {
auto start_rect = try_get_start_button(ctrler()->get_image());
if (start_rect) {
if (need_exit()) return false;
if (m_slot_fail >= slot_retry_limit) return false;
if (recruit_one(start_rect.value()))
++m_cur_times;
else
++m_slot_fail;
if (!m_has_permit && (!m_force_refresh || !m_has_refresh)) return true;
}
else {
if (!check_recruit_home_page()) return false;
Log.info("There is no available start button.");
if (!try_use_expedited) return true;
}
if (try_use_expedited) {
if (need_exit()) return false;
Log.info("ready to use expedited plan");
if (recruit_now()) {
hire_all();
}
else {
Log.info("Failed to use expedited plan");
// There is a small chance that confirm button were clicked twice and got stuck into the bottom-right
// slot. ref: #1491
if (check_recruit_home_page()) {
// ran out of expedited plan? stop trying
try_use_expedited = false;
}
else {
Log.info("Not in home page after failing to use expedited plan.");
return false;
}
}
}
}
return true;
}
std::vector<asst::TextRect> asst::AutoRecruitTask::start_recruit_analyze(const cv::Mat& image)
{
OCRer start_analyzer(image);
start_analyzer.set_task_info("StartRecruit");
if (!start_analyzer.analyze()) return {};
return start_analyzer.get_result();
}
std::optional<asst::Rect> asst::AutoRecruitTask::try_get_start_button(const cv::Mat& image)
{
const auto result = start_recruit_analyze(image);
if (result.empty()) return std::nullopt;
auto iter = ranges::find_if(
result, [&](const TextRect& r) -> bool { return !m_force_skipped.contains(slot_index_from_rect(r.rect)); });
if (iter == result.cend()) return std::nullopt;
Log.info("Found slot index", slot_index_from_rect(iter->rect), ".");
return iter->rect;
}
/// open a pending recruit slot, set timer and tags then confirm, or leave the slot doing nothing
/// return false if:
/// - recognition failed
/// - timer or tags corrupted
/// - failed to confirm
bool asst::AutoRecruitTask::recruit_one(const Rect& button)
{
LogTraceFunction;
int delay = Config.get_options().task_delay;
ctrler()->click(button);
sleep(delay);
auto calc_result = recruit_calc_task(slot_index_from_rect(button));
sleep(delay);
if (!calc_result.success) {
// recognition failed, perhaps opening the slot again would not help
{
json::value info = basic_info();
info["what"] = "RecruitError";
info["why"] = "识别错误";
callback(AsstMsg::SubTaskError, info);
}
m_force_skipped.emplace(slot_index_from_rect(button));
click_return_button();
return false;
}
if (calc_result.force_skip) {
// mark the slot as completed and return
m_force_skipped.emplace(slot_index_from_rect(button));
click_return_button();
return true;
}
if (need_exit()) return false;
if (m_set_time && !check_timer(calc_result.recruitment_time)) {
// timer was not set to 09:00:00 properly, likely the tag selection was also corrupted
// see https://github.com/MaaAssistantArknights/MaaAssistantArknights/pull/300#issuecomment-1073287984
// return and try later
Log.info("Timer of this slot has not been reduced as expected.");
click_return_button();
return false;
}
// TODO: count blue pixels and compare with number of selected tags desired
if (need_exit()) return false;
if (!confirm()) {
Log.info("Failed to confirm current recruit config.");
click_return_button();
return false;
}
return true;
}
// set recruit timer and tags only
asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc_task(slot_index index)
{
LogTraceFunction;
static constexpr size_t refresh_limit = 3;
static constexpr size_t analyze_limit = 5;
size_t refresh_count = 0;
for (size_t image_analyzer_retry = 0; image_analyzer_retry < analyze_limit;) {
++image_analyzer_retry;
RecruitImageAnalyzer image_analyzer(ctrler()->get_image());
if (!image_analyzer.analyze()) continue;
if (image_analyzer.get_tags_result().size() != RecruitConfig::CorrectNumberOfTags) continue;
const std::vector<TextRect>& tags = image_analyzer.get_tags_result();
m_has_refresh = !image_analyzer.get_refresh_rect().empty();
m_has_permit = image_analyzer.get_permit_rect().empty();
std::vector<RecruitConfig::TagId> tag_ids;
ranges::transform(tags, std::back_inserter(tag_ids), std::mem_fn(&TextRect::text));
bool has_special_tag = false;
bool has_robot_tag = false;
json::value info = basic_info();
info["details"]["tags"] = json::array(get_tag_names(tag_ids));
// tags result
{
json::value cb_info = info;
cb_info["what"] = "RecruitTagsDetected";
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
// special tags
const std::vector<RecruitConfig::TagId> SpecialTags = { "高级资深干员", "资深干员" };
if (auto special_iter = ranges::find_first_of(SpecialTags, tag_ids); special_iter != SpecialTags.cend())
[[unlikely]] {
has_special_tag = true;
json::value cb_info = info;
cb_info["what"] = "RecruitSpecialTag";
cb_info["details"]["tag"] = RecruitData.get_tag_name(*special_iter);
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
// robot tags
const std::vector<RecruitConfig::TagId> RobotTags = { "支援机械" };
if (auto robot_iter = ranges::find_first_of(RobotTags, tag_ids); robot_iter != RobotTags.cend()) [[unlikely]] {
has_robot_tag = true;
json::value cb_info = info;
cb_info["what"] = "RecruitSpecialTag";
cb_info["details"]["tag"] = RecruitData.get_tag_name(*robot_iter);
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
std::vector<RecruitCombs> result_vec = recruit_calc::get_all_combs(tag_ids);
// assuming timer would be set to 09:00:00
for (RecruitCombs& rc : result_vec) {
if (rc.min_level < 3) {
// find another min level (assuming operator list sorted in increment order by level)
auto sec = ranges::find_if(rc.opers, [](const Recruitment& op) { return op.level >= 3; });
if (sec != rc.opers.end()) {
rc.min_level = sec->level;
rc.avg_level = std::transform_reduce(sec, rc.opers.end(), 0., std::plus<double> {},
std::mem_fn(&Recruitment::level)) /
static_cast<double>(std::distance(sec, rc.opers.end()));
}
}
}
ranges::sort(result_vec, [&](const RecruitCombs& lhs, const RecruitCombs& rhs) -> bool {
// prefer the one with special tag
// workaround for https://github.com/MaaAssistantArknights/MaaAssistantArknights/issues/1336
if (has_special_tag) {
bool l_has = ranges::find_first_of(lhs.tags, SpecialTags) != lhs.tags.cend();
bool r_has = ranges::find_first_of(rhs.tags, SpecialTags) != rhs.tags.cend();
if (l_has != r_has) return l_has > r_has;
}
if (lhs.min_level != rhs.min_level)
return lhs.min_level > rhs.min_level; // 最小等级大的,排前面
else if (lhs.max_level != rhs.max_level)
return lhs.max_level > rhs.max_level; // 最大等级大的,排前面
else if (std::fabs(lhs.avg_level - rhs.avg_level) > DoubleDiff)
return lhs.avg_level > rhs.avg_level; // 平均等级高的,排前面
else
return lhs.tags.size() < rhs.tags.size(); // Tag数量少的排前面
});
if (result_vec.empty()) continue;
const auto& final_combination = result_vec.front();
{
json::value results_json;
results_json["result"] = json::array();
results_json["level"] = final_combination.min_level;
std::vector<json::value> result_json_vector;
for (const auto& comb : result_vec) {
json::value comb_json;
comb_json["tags"] = json::array(get_tag_names(comb.tags));
std::vector<json::value> opers_json_vector;
for (const Recruitment& oper_info : ranges::reverse_view(comb.opers)) { // print reversely
json::value oper_json;
oper_json["name"] = oper_info.name;
oper_json["id"] = oper_info.id;
oper_json["level"] = oper_info.level;
opers_json_vector.emplace_back(std::move(oper_json));
}
comb_json["opers"] = json::array(std::move(opers_json_vector));
comb_json["level"] = comb.min_level;
results_json["result"].as_array().emplace_back(std::move(comb_json));
}
info["details"] |= results_json.as_object();
json::value cb_info = info;
cb_info["what"] = "RecruitResult";
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
bool to_report = false;
if (!is_calc_only_task()) {
// report if the slot is clean
if (!m_dirty_slots.contains(index)) {
to_report = true;
m_dirty_slots.emplace(index); // mark as dirty
}
else
Log.info("will not report, dirty slots are", m_dirty_slots);
}
#ifdef ASST_DEBUG
to_report = true;
#endif
if (to_report) {
upload_result(tag_ids, info["details"]);
}
if (need_exit()) return {};
// refresh
if (m_need_refresh && m_has_refresh && !has_special_tag && final_combination.min_level == 3 &&
!(m_skip_robot && has_robot_tag)) {
if (refresh_count > refresh_limit) [[unlikely]] {
json::value cb_info = basic_info();
cb_info["what"] = "RecruitError";
cb_info["why"] = "刷新次数达到上限";
cb_info["details"] = json::object { { "refresh_limit", refresh_limit } };
callback(AsstMsg::SubTaskError, cb_info);
return {};
}
refresh();
++refresh_count;
// mark the slot clean after refreshed
m_dirty_slots.erase(index);
{
json::value cb_info = basic_info();
cb_info["what"] = "RecruitTagsRefreshed";
cb_info["details"] = json::object {
{ "count", refresh_count },
{ "refresh_limit", refresh_limit },
};
callback(AsstMsg::SubTaskExtraInfo, cb_info);
Log.trace("recruit tags refreshed", std::to_string(refresh_count), " times, rerunning recruit task");
}
// desired retry, not an error
--image_analyzer_retry;
continue;
}
if (need_exit()) return {};
if (!m_has_permit) {
bool continue_refresh = m_force_refresh && m_has_refresh;
json::value cb_info = basic_info();
cb_info["what"] = "RecruitNoPermit";
cb_info["details"] = json::object {
{ "continue", continue_refresh },
};
callback(AsstMsg::SubTaskExtraInfo, cb_info);
Log.trace("No recruit permit");
calc_task_result_type result;
result.success = true;
result.force_skip = true;
return result;
}
if (!is_calc_only_task()) {
// do not confirm, force skip
if (ranges::none_of(m_confirm_level, [&](const auto& i) { return i == final_combination.min_level; })) {
calc_task_result_type result;
result.success = true;
result.force_skip = true;
return result;
}
if (m_skip_robot && has_robot_tag) {
calc_task_result_type result;
result.success = true;
result.force_skip = true;
return result;
}
}
int recruitment_time = m_desired_time_map[(std::max)(final_combination.min_level, 3)];
if (recruitment_time == 0) recruitment_time = 9 * 60;
// try to set the timer to desired value
if (m_set_time) {
Log.info("recruitment time:", recruitment_time, "min");
const int desired_hour = recruitment_time / 60;
const int desired_minute_div_10 = (recruitment_time % 60) / 10;
const int temp = desired_hour + (desired_minute_div_10 != 0);
const int hour_delta = (1 < temp) ? (1 + 9 - temp) : (temp - 1);
const int minute_delta = (0 < desired_minute_div_10) ? (0 + 6 - desired_minute_div_10) : (0);
for (int i = 0; i < hour_delta; ++i)
ctrler()->click(image_analyzer.get_hour_decrement_rect());
for (int i = 0; i < minute_delta; ++i)
ctrler()->click(image_analyzer.get_minute_decrement_rect());
}
// nothing to select, leave the selection empty
if (ranges::none_of(m_select_level, [&](const auto& i) { return i == final_combination.min_level; })) {
calc_task_result_type result;
result.success = true;
result.force_skip = false;
result.recruitment_time = recruitment_time;
result.tags_selected = 0;
return result;
}
auto final_select =
(m_select_extra_tags_mode != ExtraTagsMode::NoExtra) ? get_select_tags(result_vec) : final_combination.tags;
// select tags
for (const std::string& final_tag_name : final_select) {
auto tag_rect_iter = ranges::find_if(tags, [&](const TextRect& r) { return r.text == final_tag_name; });
if (tag_rect_iter != tags.cend()) {
ctrler()->click(tag_rect_iter->rect);
}
}
{
json::value cb_info = basic_info();
cb_info["what"] = "RecruitTagsSelected";
cb_info["details"] = json::object { { "tags", json::array(get_tag_names(final_select)) } };
callback(AsstMsg::SubTaskExtraInfo, cb_info);
}
calc_task_result_type result;
result.success = true;
result.force_skip = false;
result.recruitment_time = recruitment_time;
result.tags_selected = static_cast<int>(final_combination.tags.size());
return result;
}
return {};
}
bool asst::AutoRecruitTask::recruit_begin()
{
ProcessTask task(*this, { "RecruitBegin" });
return task.run();
}
bool asst::AutoRecruitTask::check_timer(int minutes_expected)
{
const auto image = ctrler()->get_image();
const auto replace_map = Task.get<OcrTaskInfo>("NumberOcrReplace")->replace_map;
{
OCRer hour_ocr(image);
hour_ocr.set_task_info("RecruitTimerH");
hour_ocr.set_replace(replace_map);
if (!hour_ocr.analyze()) return false;
std::string desired_hour_str = std::string("0") + std::to_string(minutes_expected / 60);
if (hour_ocr.get_result().front().text != desired_hour_str) return false;
}
if (minutes_expected % 60 == 0) return true; // minute counter stays untouched
{
OCRer minute_ocr(image);
minute_ocr.set_task_info("RecruitTimerM");
minute_ocr.set_replace(replace_map);
if (!minute_ocr.analyze()) return false;
std::string desired_minute_str = std::to_string((minutes_expected % 60) / 10) + "0";
if (minute_ocr.get_result().front().text != desired_minute_str) return false;
}
return true;
}
bool asst::AutoRecruitTask::check_recruit_home_page()
{
ProcessTask task(*this, { "RecruitFlag" });
task.set_retry_times(2);
return task.run();
}
bool asst::AutoRecruitTask::recruit_now()
{
ProcessTask task(*this, { "RecruitNow" });
return task.run();
}
bool asst::AutoRecruitTask::confirm()
{
ProcessTask confirm_task(*this, { "RecruitConfirm" });
return confirm_task.set_retry_times(5).run();
}
bool asst::AutoRecruitTask::refresh()
{
ProcessTask refresh_task(*this, { "RecruitRefresh" });
return refresh_task.run();
}
bool asst::AutoRecruitTask::hire_all(const cv::Mat& image)
{
LogTraceFunction;
// mark slots with *Hire* button clean (regardless of whether hiring will success)
{
MultiMatcher hire_searcher(image);
hire_searcher.set_task_info("RecruitFinish");
hire_searcher.analyze();
for (const MatchRect& r : hire_searcher.get_result()) {
Log.info("Mark", slot_index_from_rect(r.rect), "clean");
m_dirty_slots.erase(slot_index_from_rect(r.rect));
}
if (hire_searcher.get_result().empty()) return true;
}
// hire all
return ProcessTask { *this, { "RecruitFinish" } }.run();
}
/// search for blue *Hire* buttons in the recruit home page, mark those slot clean and do hiring
bool asst::AutoRecruitTask::hire_all()
{
return hire_all(ctrler()->get_image());
}
/// search for *RecruitNow* buttons before recruit and mark them as dirty
[[maybe_unused]] bool asst::AutoRecruitTask::initialize_dirty_slot_info(const cv::Mat& image)
{
m_dirty_slots.clear();
const auto result = start_recruit_analyze(image);
for (const TextRect& r : result) {
m_dirty_slots.emplace(slot_index_from_rect(r.rect));
}
Log.info("Dirty slots are", m_dirty_slots);
return true;
}
std::vector<std::string> asst::AutoRecruitTask::get_tag_names(const std::vector<RecruitConfig::TagId>& ids) const
{
std::vector<std::string> names;
for (const RecruitConfig::TagId& id : ids) {
names.emplace_back(RecruitData.get_tag_name(id));
}
return names;
}
std::vector<std::string> asst::AutoRecruitTask::get_select_tags(const std::vector<RecruitCombs>& combinations)
{
LogTraceFunction;
std::unordered_set<std::string> unique_tags;
std::vector<std::string> select;
if (m_select_extra_tags_mode == ExtraTagsMode::Extra) {
while (select.size() < 3) {
for (const asst::RecruitCombs& comb : combinations)
for (const std::string& tag : comb.tags) {
if (unique_tags.find(tag) == unique_tags.cend()) {
unique_tags.insert(tag);
select.emplace_back(tag);
if (select.size() == 3) return select;
}
}
}
}
else if (m_select_extra_tags_mode == ExtraTagsMode::ExtraOnlyRare) {
// only select rare tags ( > 3 rank) and select as many as possible.
// do not select lower rank tags when higher rank tags exist.
int min_level = combinations.front().min_level;
// tag combo will be either full selected, or abandoned.
int emplace_back_count = 0;
if (min_level == 3) return select;
for (const asst::RecruitCombs& comb : combinations) {
if (comb.min_level < min_level) return select;
emplace_back_count = 0;
for (const std::string& tag : comb.tags) {
if (unique_tags.find(tag) == unique_tags.cend()) {
unique_tags.insert(tag);
select.emplace_back(tag);
++emplace_back_count;
}
}
if (select.size() > 3) {
while (emplace_back_count--) {
unique_tags.erase(select.back());
select.pop_back();
}
}
}
}
return select;
}
template <typename Rng>
void asst::AutoRecruitTask::upload_result(const Rng& tag_ids, const json::value& yituliu_details)
{
LogTraceFunction;
if (m_upload_to_penguin) {
upload_to_penguin(tag_ids);
}
if (m_upload_to_yituliu) {
upload_to_yituliu(yituliu_details);
}
}
template <typename Rng>
void asst::AutoRecruitTask::upload_to_penguin(Rng&& tags)
{
LogTraceFunction;
json::value body;
body["server"] = m_server;
body["stageId"] = "recruit";
auto& all_drops = body["drops"];
for (const auto& tag : tags) {
all_drops.emplace(json::object {
{ "dropType", "NORMAL_DROP" },
{ "itemId", tag },
{ "quantity", 1 },
});
}
body["source"] = UploadDataSource;
body["version"] = Version;
std::unordered_map<std::string, std::string> extra_headers;
if (!m_penguin_id.empty()) {
extra_headers = { { "authorization", "PenguinID " + m_penguin_id } };
}
if (!m_report_penguin_task_ptr) {
m_report_penguin_task_ptr = std::make_shared<ReportDataTask>(report_penguin_callback, this);
}
m_report_penguin_task_ptr->set_report_type(ReportType::PenguinStats)
.set_body(body.to_string())
.set_extra_headers(extra_headers)
.set_retry_times(5)
.run();
}
void asst::AutoRecruitTask::upload_to_yituliu(const json::value& details)
{
LogTraceFunction;
json::value body = details;
body["server"] = m_server;
body["source"] = UploadDataSource;
body["version"] = Version;
body["uuid"] = /* m_yituliu_id */ m_penguin_id;
if (!m_report_yituliu_task_ptr) {
m_report_yituliu_task_ptr = std::make_shared<ReportDataTask>(report_yituliu_callback, this);
}
m_report_yituliu_task_ptr->set_report_type(ReportType::YituliuBigDataAutoRecruit)
.set_body(body.to_string())
.set_retry_times(0)
.run();
}
void asst::AutoRecruitTask::report_penguin_callback(AsstMsg msg, const json::value& detail, AbstractTask* task_ptr)
{
LogTraceFunction;
auto p_this = dynamic_cast<AutoRecruitTask*>(task_ptr);
if (!p_this) {
return;
}
if (msg == AsstMsg::SubTaskExtraInfo && detail.get("what", std::string()) == "PenguinId") {
std::string id = detail.get("details", "id", std::string());
p_this->m_penguin_id = id;
}
p_this->callback(msg, detail);
}
void asst::AutoRecruitTask::report_yituliu_callback(AsstMsg msg, const json::value& detail, AbstractTask* task_ptr)
{
LogTraceFunction;
auto p_this = dynamic_cast<AutoRecruitTask*>(task_ptr);
if (!p_this) {
return;
}
p_this->callback(msg, detail);
}