Files
MaaAssistantArknights/src/MeoAssistant/Assistant.cpp

642 lines
19 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 "Assistant.h"
#include <filesystem>
#include <time.h>
#include <meojson/json.h>
#include <opencv2/opencv.hpp>
#include "AsstUtils.hpp"
#include "Controller.h"
#include "Logger.hpp"
#include "Resource.h"
#include "CreditShoppingTask.h"
#include "InfrastDormTask.h"
#include "InfrastInfoTask.h"
#include "InfrastMfgTask.h"
#include "InfrastOfficeTask.h"
#include "InfrastPowerTask.h"
#include "InfrastReceptionTask.h"
#include "InfrastTradeTask.h"
#include "ProcessTask.h"
#include "RecruitTask.h"
#include "AutoRecruitTask.h"
#include "InfrastControlTask.h"
#include "RuntimeStatus.h"
using namespace asst;
Assistant::Assistant(std::string dirname, AsstCallback callback, void* callback_arg)
: m_dirname(std::move(dirname) + "/"),
m_callback(callback),
m_callback_arg(callback_arg)
{
Logger::set_dirname(m_dirname);
Controller::set_dirname(m_dirname);
LogTraceFunction;
bool resource_ret = Resrc.load(m_dirname + "Resource/");
if (!resource_ret) {
const std::string& error = Resrc.get_last_error();
Log.error("resource broken:", error);
if (m_callback == nullptr) {
throw error;
}
json::value callback_json;
callback_json["type"] = "resource broken";
callback_json["what"] = error;
m_callback(AsstMsg::InitFaild, callback_json, m_callback_arg);
throw error;
}
m_working_thread = std::thread(std::bind(&Assistant::working_proc, this));
m_msg_thread = std::thread(std::bind(&Assistant::msg_proc, this));
}
Assistant::~Assistant()
{
LogTraceFunction;
m_thread_exit = true;
m_thread_idle = true;
m_condvar.notify_all();
m_msg_condvar.notify_all();
if (m_working_thread.joinable()) {
m_working_thread.join();
}
if (m_msg_thread.joinable()) {
m_msg_thread.join();
}
}
bool asst::Assistant::catch_default()
{
LogTraceFunction;
auto& opt = Resrc.cfg().get_options();
switch (opt.connect_type) {
case ConnectType::Emulator:
return catch_emulator();
case ConnectType::Custom:
return catch_custom();
default:
return false;
}
}
bool Assistant::catch_emulator(const std::string& emulator_name)
{
LogTraceFunction;
stop();
#ifdef _WIN32
bool ret = false;
//std::string cor_name = emulator_name;
auto& cfg = Resrc.cfg();
std::unique_lock<std::mutex> lock(m_mutex);
// 自动匹配模拟器,逐个找
if (emulator_name.empty()) {
for (const auto& [name, info] : cfg.get_emulators_info()) {
ret = Ctrler.try_capture(info);
if (ret) {
//cor_name = name;
break;
}
}
}
else { // 指定的模拟器
auto& info = cfg.get_emulators_info().at(emulator_name);
ret = Ctrler.try_capture(info);
}
m_inited = ret;
return ret;
#else // Not supported catch emulator in Linux
return false;
#endif
}
bool asst::Assistant::catch_custom(const std::string& address)
{
LogTraceFunction;
stop();
bool ret = false;
auto& cfg = Resrc.cfg();
std::unique_lock<std::mutex> lock(m_mutex);
EmulatorInfo remote_info = cfg.get_emulators_info().at("Custom");
if (!address.empty()) {
remote_info.adb.addresses.push_back(address);
}
ret = Ctrler.try_capture(remote_info, true);
m_inited = ret;
return ret;
}
bool asst::Assistant::catch_fake()
{
LogTraceFunction;
stop();
m_inited = true;
return true;
}
bool asst::Assistant::append_start_up(bool only_append)
{
LogTraceFunction;
if (!m_inited) {
return false;
}
std::unique_lock<std::mutex> lock(m_mutex);
auto task_ptr = std::make_shared<ProcessTask>(task_callback, (void*)this);
task_ptr->set_task_chain("StartUp");
task_ptr->set_tasks({ "StartUp" });
task_ptr->set_times_limit("ReturnToTerminal", 0);
task_ptr->set_times_limit("Terminal", 0);
m_tasks_queue.emplace(task_ptr);
if (!only_append) {
return start(false);
}
return true;
}
bool asst::Assistant::append_fight(const std::string& stage, int mecidine, int stone, int times, bool only_append)
{
LogTraceFunction;
if (!m_inited) {
return false;
}
constexpr const char* TaskChain = "Fight";
// 进入选关界面(主界面的“终端”点进去)
auto terminal_task_ptr = std::make_shared<ProcessTask>(task_callback, (void*)this);
terminal_task_ptr->set_task_chain(TaskChain);
terminal_task_ptr->set_tasks({ "StageBegin" });
terminal_task_ptr->set_times_limit("LastBattle", 0);
// 进入对应的关卡
auto stage_task_ptr = std::make_shared<ProcessTask>(task_callback, (void*)this);
stage_task_ptr->set_task_chain(TaskChain);
stage_task_ptr->set_tasks({ stage });
// 开始战斗任务
auto fight_task_ptr = std::make_shared<ProcessTask>(task_callback, (void*)this);
fight_task_ptr->set_task_chain(TaskChain);
fight_task_ptr->set_tasks({ "FightBegin" });
fight_task_ptr->set_times_limit("MedicineConfirm", mecidine);
fight_task_ptr->set_times_limit("StoneConfirm", stone);
fight_task_ptr->set_times_limit("StartButton1", times);
std::unique_lock<std::mutex> lock(m_mutex);
if (!stage.empty()) {
m_tasks_queue.emplace(terminal_task_ptr);
m_tasks_queue.emplace(stage_task_ptr);
}
m_tasks_queue.emplace(fight_task_ptr);
if (!only_append) {
return start(false);
}
return true;
}
bool asst::Assistant::append_award(bool only_append)
{
return append_process_task("AwardBegin", "Award");
}
bool asst::Assistant::append_visit(bool only_append)
{
return append_process_task("VisitBegin", "Visit");
}
bool asst::Assistant::append_mall(bool with_shopping, bool only_append)
{
LogTraceFunction;
if (!m_inited) {
return false;
}
std::unique_lock<std::mutex> lock(m_mutex);
const std::string task_chain = "Mall";
append_process_task("MallBegin", task_chain);
if (with_shopping) {
auto shopping_task_ptr = std::make_shared<CreditShoppingTask>(task_callback, (void*)this);
shopping_task_ptr->set_task_chain(task_chain);
m_tasks_queue.emplace(shopping_task_ptr);
}
if (!only_append) {
return start(false);
}
return true;
}
bool Assistant::append_process_task(const std::string& task, std::string task_chain, int retry_times)
{
LogTraceFunction;
if (!m_inited) {
return false;
}
//std::unique_lock<std::mutex> lock(m_mutex);
if (task_chain.empty()) {
task_chain = task;
}
auto task_ptr = std::make_shared<ProcessTask>(task_callback, (void*)this);
task_ptr->set_task_chain(task_chain);
task_ptr->set_tasks({ task });
task_ptr->set_retry_times(retry_times);
m_tasks_queue.emplace(task_ptr);
//if (!only_append) {
// return start(false);
//}
return true;
}
bool asst::Assistant::append_recruit(unsigned max_times, const std::vector<int>& select_level, const std::vector<int>& confirm_level, bool need_refresh)
{
LogTraceFunction;
if (!m_inited) {
return false;
}
static const std::string TaskChain = "Recruit";
append_process_task("RecruitBegin", TaskChain);
auto recruit_task_ptr = std::make_shared<AutoRecruitTask>(task_callback, (void*)this);
recruit_task_ptr->set_max_times(max_times);
recruit_task_ptr->set_need_refresh(need_refresh);
recruit_task_ptr->set_select_level(select_level);
recruit_task_ptr->set_confirm_level(confirm_level);
recruit_task_ptr->set_task_chain(TaskChain);
recruit_task_ptr->set_retry_times(AutoRecruitTaskRetryTimesDefault);
m_tasks_queue.emplace(recruit_task_ptr);
return true;
}
#ifdef LOG_TRACE
bool Assistant::append_debug()
{
LogTraceFunction;
if (!m_inited) {
return false;
}
std::unique_lock<std::mutex> lock(m_mutex);
{
constexpr static const char* DebugTaskChain = "Debug";
auto shift_task_ptr = std::make_shared<InfrastControlTask>(task_callback, (void*)this);
shift_task_ptr->set_work_mode(infrast::WorkMode::Aggressive);
shift_task_ptr->set_facility("Control");
shift_task_ptr->set_product("MoodAddition");
shift_task_ptr->set_task_chain(DebugTaskChain);
m_tasks_queue.emplace(shift_task_ptr);
}
return true;
}
#endif
bool Assistant::start_recruit_calc(const std::vector<int>& select_level, bool set_time)
{
LogTraceFunction;
if (!m_inited) {
return false;
}
std::unique_lock<std::mutex> lock(m_mutex);
auto task_ptr = std::make_shared<RecruitTask>(task_callback, (void*)this);
task_ptr->set_param(select_level, set_time);
task_ptr->set_retry_times(OpenRecruitTaskRetryTimesDefault);
task_ptr->set_task_chain("RecruitCalc");
m_tasks_queue.emplace(task_ptr);
return start(false);
}
bool asst::Assistant::append_infrast(infrast::WorkMode work_mode, const std::vector<std::string>& order, const std::string& uses_of_drones, double dorm_threshold, bool only_append)
{
LogTraceFunction;
if (!m_inited) {
return false;
}
// 保留接口,目前强制按激进模式进行换班
work_mode = infrast::WorkMode::Aggressive;
constexpr static const char* InfrastTaskCahin = "Infrast";
// 这个流程任务,结束的时候是处于基建主界面的。既可以用于进入基建,也可以用于从设施里返回基建主界面
auto append_infrast_begin = [&]() {
append_process_task("InfrastBegin", InfrastTaskCahin);
};
append_infrast_begin();
auto info_task_ptr = std::make_shared<InfrastInfoTask>(task_callback, (void*)this);
info_task_ptr->set_work_mode(work_mode);
info_task_ptr->set_task_chain(InfrastTaskCahin);
info_task_ptr->set_mood_threshold(dorm_threshold);
m_tasks_queue.emplace(info_task_ptr);
// 因为后期要考虑多任务间的联动等所以这些任务的声明暂时不放到for循环中
auto mfg_task_ptr = std::make_shared<InfrastMfgTask>(task_callback, (void*)this);
mfg_task_ptr->set_work_mode(work_mode);
mfg_task_ptr->set_task_chain(InfrastTaskCahin);
mfg_task_ptr->set_mood_threshold(dorm_threshold);
mfg_task_ptr->set_uses_of_drone(uses_of_drones);
auto trade_task_ptr = std::make_shared<InfrastTradeTask>(task_callback, (void*)this);
trade_task_ptr->set_work_mode(work_mode);
trade_task_ptr->set_task_chain(InfrastTaskCahin);
trade_task_ptr->set_mood_threshold(dorm_threshold);
trade_task_ptr->set_uses_of_drone(uses_of_drones);
auto power_task_ptr = std::make_shared<InfrastPowerTask>(task_callback, (void*)this);
power_task_ptr->set_work_mode(work_mode);
power_task_ptr->set_task_chain(InfrastTaskCahin);
power_task_ptr->set_mood_threshold(dorm_threshold);
auto office_task_ptr = std::make_shared<InfrastOfficeTask>(task_callback, (void*)this);
office_task_ptr->set_work_mode(work_mode);
office_task_ptr->set_task_chain(InfrastTaskCahin);
office_task_ptr->set_mood_threshold(dorm_threshold);
auto recpt_task_ptr = std::make_shared<InfrastReceptionTask>(task_callback, (void*)this);
recpt_task_ptr->set_work_mode(work_mode);
recpt_task_ptr->set_task_chain(InfrastTaskCahin);
recpt_task_ptr->set_mood_threshold(dorm_threshold);
auto control_task_ptr = std::make_shared<InfrastControlTask>(task_callback, (void*)this);
control_task_ptr->set_work_mode(work_mode);
control_task_ptr->set_task_chain(InfrastTaskCahin);
control_task_ptr->set_mood_threshold(dorm_threshold);
auto dorm_task_ptr = std::make_shared<InfrastDormTask>(task_callback, (void*)this);
dorm_task_ptr->set_work_mode(work_mode);
dorm_task_ptr->set_task_chain(InfrastTaskCahin);
dorm_task_ptr->set_mood_threshold(dorm_threshold);
for (const auto& facility : order) {
if (facility == "Dorm") {
m_tasks_queue.emplace(dorm_task_ptr);
}
else if (facility == "Mfg") {
m_tasks_queue.emplace(mfg_task_ptr);
}
else if (facility == "Trade") {
m_tasks_queue.emplace(trade_task_ptr);
}
else if (facility == "Power") {
m_tasks_queue.emplace(power_task_ptr);
}
else if (facility == "Office") {
m_tasks_queue.emplace(office_task_ptr);
}
else if (facility == "Reception") {
m_tasks_queue.emplace(recpt_task_ptr);
}
else if (facility == "Control") {
m_tasks_queue.emplace(control_task_ptr);
}
else {
Log.error("append_infrast | Unknown facility", facility);
}
append_infrast_begin();
}
if (!only_append) {
return start(false);
}
return true;
}
void asst::Assistant::set_penguin_id(const std::string& id)
{
auto& opt = Resrc.cfg().get_options();
if (id.empty()) {
opt.penguin_report.extra_param.clear();
}
else {
opt.penguin_report.extra_param = "-H \"authorization: PenguinID " + id + "\"";
}
}
bool asst::Assistant::start(bool block)
{
LogTraceFunction;
Log.trace("Start |", block ? "block" : "non block");
if (!m_thread_idle || !m_inited) {
return false;
}
std::unique_lock<std::mutex> lock;
if (block) { // 外部调用
lock = std::unique_lock<std::mutex>(m_mutex);
}
m_thread_idle = false;
m_condvar.notify_one();
return true;
}
bool Assistant::stop(bool block)
{
LogTraceFunction;
Log.trace("Stop |", block ? "block" : "non block");
m_thread_idle = true;
std::unique_lock<std::mutex> lock;
if (block) { // 外部调用
lock = std::unique_lock<std::mutex>(m_mutex);
}
decltype(m_tasks_queue) empty;
m_tasks_queue.swap(empty);
clear_cache();
return true;
}
void Assistant::working_proc()
{
LogTraceFunction;
std::string pre_taskchain;
while (!m_thread_exit) {
//LogTraceScope("Assistant::working_proc Loop");
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_thread_idle && !m_tasks_queue.empty()) {
auto start_time = std::chrono::steady_clock::now();
auto task_ptr = m_tasks_queue.front();
std::string cur_taskchain = task_ptr->get_task_chain();
json::value task_json = json::object{
{"task_chain", cur_taskchain},
};
if (cur_taskchain != pre_taskchain) {
task_callback(AsstMsg::TaskChainStart, task_json, this);
pre_taskchain = cur_taskchain;
}
task_ptr->set_exit_flag(&m_thread_idle);
bool ret = task_ptr->run();
m_tasks_queue.pop();
if (!ret) {
task_callback(AsstMsg::TaskError, task_json, this);
}
else if (m_tasks_queue.empty() || cur_taskchain != m_tasks_queue.front()->get_task_chain()) {
task_callback(AsstMsg::TaskChainCompleted, task_json, this);
}
if (m_tasks_queue.empty()) {
task_callback(AsstMsg::AllTasksCompleted, task_json, this);
}
//clear_cache();
auto& delay = Resrc.cfg().get_options().task_delay;
m_condvar.wait_until(lock, start_time + std::chrono::milliseconds(delay),
[&]() -> bool { return m_thread_idle; });
}
else {
pre_taskchain.clear();
m_thread_idle = true;
Log.flush();
//controller.set_idle(true);
m_condvar.wait(lock);
}
}
}
void Assistant::msg_proc()
{
LogTraceFunction;
while (!m_thread_exit) {
//LogTraceScope("Assistant::msg_proc Loop");
std::unique_lock<std::mutex> lock(m_msg_mutex);
if (!m_msg_queue.empty()) {
// 结构化绑定只能是引用后续的pop会使引用失效所以需要重新构造一份这里采用了move的方式
auto&& [temp_msg, temp_detail] = m_msg_queue.front();
AsstMsg msg = std::move(temp_msg);
json::value detail = std::move(temp_detail);
m_msg_queue.pop();
lock.unlock();
if (m_callback) {
m_callback(msg, detail, m_callback_arg);
}
}
else {
m_msg_condvar.wait(lock);
}
}
}
void Assistant::task_callback(AsstMsg msg, const json::value& detail, void* custom_arg)
{
Log.trace("Assistant::task_callback |", msg, detail.to_string());
Assistant* p_this = (Assistant*)custom_arg;
json::value more_detail = detail;
switch (msg) {
case AsstMsg::PtrIsNull:
case AsstMsg::ImageIsEmpty:
p_this->stop(false);
break;
case AsstMsg::StageDrops:
more_detail = p_this->organize_stage_drop(more_detail);
break;
default:
break;
}
// Todo: 有些不需要回调给外部的消息,得在这里给拦截掉
// 加入回调消息队列,由回调消息线程外抛给外部
p_this->append_callback(msg, std::move(more_detail));
}
void asst::Assistant::append_callback(AsstMsg msg, json::value detail)
{
std::unique_lock<std::mutex> lock(m_msg_mutex);
m_msg_queue.emplace(msg, std::move(detail));
m_msg_condvar.notify_one();
}
void Assistant::clear_cache()
{
Resrc.item().clear_drop_count();
//task.clear_cache();
}
json::value asst::Assistant::organize_stage_drop(const json::value& rec)
{
json::value dst = rec;
auto& item = Resrc.item();
for (json::value& drop : dst["drops"].as_array()) {
std::string id = drop["itemId"].as_string();
int quantity = drop["quantity"].as_integer();
item.increase_drop_count(id, quantity);
const std::string& name = item.get_item_name(id);
drop["itemName"] = name.empty() ? "未知材料" : name;
}
std::vector<json::value> statistics_vec;
for (auto&& [id, count] : item.get_drop_count()) {
json::value info;
info["itemId"] = id;
const std::string& name = item.get_item_name(id);
info["itemName"] = name.empty() ? "未知材料" : name;
info["count"] = count;
statistics_vec.emplace_back(std::move(info));
}
//// 排个序,数量多的放前面
//std::sort(statistics_vec.begin(), statistics_vec.end(),
// [](const json::value& lhs, const json::value& rhs) -> bool {
// return lhs.at("count").as_integer() > rhs.at("count").as_integer();
// });
dst["statistics"] = json::array(std::move(statistics_vec));
Log.trace("organize_stage_drop | ", dst.to_string());
return dst;
}