Files
MaaAssistantArknights/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp
Status102 88a1e4fb68 rft: 干员数据重构, 支持跨职业重名干员 (#16084)
* rft: 干员数据重构, 支持跨职业重名干员

* fix: 查找优化

* fix: default value

* perf: 重新加载时清空数据

* fix: 默认值

* perf: 查找
2026-04-08 11:39:52 +08:00

320 lines
14 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 "CopilotConfig.h"
#include <meojson/json.hpp>
#include "Config/Miscellaneous/BattleDataConfig.h"
#include "TilePack.h"
#include "Utils/Logger.hpp"
using namespace asst::battle;
using namespace asst::battle::copilot;
void asst::CopilotConfig::clear()
{
m_data = decltype(m_data)();
}
bool asst::CopilotConfig::parse(const json::value& json)
{
LogTraceFunction;
clear();
m_data.info = parse_basic_info(json);
m_data.groups = parse_groups(json);
m_data.actions = parse_actions(json);
return true;
}
asst::battle::copilot::BasicInfo asst::CopilotConfig::parse_basic_info(const json::value& json)
{
LogTraceFunction;
battle::copilot::BasicInfo info;
info.stage_name = json.at("stage_name").as_string();
info.title = json.get("doc", "title", std::string());
info.title_color = json.get("doc", "title_color", std::string());
info.details = json.get("doc", "details", std::string());
info.details_color = json.get("doc", "details_color", std::string());
return info;
}
asst::battle::copilot::OperUsageGroups asst::CopilotConfig::parse_groups(const json::value& json)
{
LogTraceFunction;
battle::copilot::OperUsageGroups groups;
if (auto opt = json.find<json::array>("opers")) {
for (const auto& oper_info : opt.value()) {
OperUsage oper;
oper.name = oper_info.at("name").as_string();
oper.skill = oper_info.get("skill", 0);
oper.skill_usage = static_cast<battle::SkillUsage>(oper_info.get("skill_usage", 0));
oper.skill_times = oper_info.get("skill_times", 1); // 使用技能的次数,默认为 1兼容曾经的作业
int rarity = BattleDataConfig::get_instance().get_rarity(oper.name); // 兼容古早旧作业中非法的技能选择
if (oper.skill == 3 && rarity < 6) {
LogError << __FUNCTION__ << "| Oper " << oper.name << " with rarity " << rarity
<< " cannot use skill index 3, set to 0.";
oper.skill = 0;
}
else if (oper.skill == 2 && rarity < 4) {
LogError << __FUNCTION__ << "| Oper " << oper.name << " with rarity " << rarity
<< " cannot use skill index 2, set to 0.";
oper.skill = 0;
}
else if (oper.skill == 1 && rarity < 3) {
LogError << __FUNCTION__ << "| Oper " << oper.name << " with rarity " << rarity
<< " cannot use skill index 1, set to 0.";
oper.skill = 0;
}
// 解析练度需求
if (auto req_opt = oper_info.find("requirements")) {
oper.requirements.elite = req_opt->get("elite", 0);
oper.requirements.level = req_opt->get("level", 0);
oper.requirements.skill_level = req_opt->get("skill_level", 0);
oper.requirements.module = req_opt->get("module", -1);
// oper.requirements.potentiality = req_opt->get("potentiality", 0);
}
// 单个干员的,干员名直接作为组名
std::string group_name = oper.name;
groups.emplace_back(OperUsageGroup { std::move(group_name), std::vector { std::move(oper) } });
}
}
if (auto opt = json.find<json::array>("groups")) {
for (const auto& group_info : opt.value()) {
std::string group_name = group_info.at("name").as_string();
std::vector<OperUsage> oper_vec;
for (const auto& oper_info : group_info.at("opers").as_array()) {
OperUsage oper;
oper.name = oper_info.at("name").as_string();
oper.skill = oper_info.get("skill", 0);
oper.skill_usage = static_cast<battle::SkillUsage>(oper_info.get("skill_usage", 0));
oper.skill_times = oper_info.get("skill_times", 1); // 使用技能的次数,默认为 1兼容曾经的作业
int rarity = BattleDataConfig::get_instance().get_rarity(oper.name); // 兼容古早旧作业中非法的技能选择
if (oper.skill == 3 && rarity < 6) {
LogError << __FUNCTION__ << "| Oper " << oper.name << " with rarity " << rarity
<< " cannot use skill index 3, set to 0.";
oper.skill = 0;
}
else if (oper.skill == 2 && rarity < 4) {
LogError << __FUNCTION__ << "| Oper " << oper.name << " with rarity " << rarity
<< " cannot use skill index 2, set to 0.";
oper.skill = 0;
}
else if (oper.skill == 1 && rarity < 3) {
LogError << __FUNCTION__ << "| Oper " << oper.name << " with rarity " << rarity
<< " cannot use skill index 1, set to 0.";
oper.skill = 0;
}
// 解析练度需求
if (auto req_opt = oper_info.find("requirements")) {
oper.requirements.elite = req_opt->get("elite", 0);
oper.requirements.level = req_opt->get("level", 0);
oper.requirements.skill_level = req_opt->get("skill_level", 0);
oper.requirements.module = req_opt->get("module", -1);
// oper.requirements.potentiality = req_opt->get("potentiality", 0);
}
oper_vec.emplace_back(std::move(oper));
}
groups.emplace_back(OperUsageGroup { std::move(group_name), std::move(oper_vec) });
}
}
return groups;
}
std::vector<asst::battle::copilot::Action> asst::CopilotConfig::parse_actions(const json::value& json)
{
LogTraceFunction;
std::vector<battle::copilot::Action> actions_list;
for (const auto& action_info : json.at("actions").as_array()) {
Action action;
static const std::unordered_map<std::string, ActionType> ActionTypeMapping = {
{ "Deploy", ActionType::Deploy },
{ "DEPLOY", ActionType::Deploy },
{ "deploy", ActionType::Deploy },
{ "部署", ActionType::Deploy },
{ "Skill", ActionType::UseSkill },
{ "SKILL", ActionType::UseSkill },
{ "skill", ActionType::UseSkill },
{ "技能", ActionType::UseSkill },
{ "Retreat", ActionType::Retreat },
{ "RETREAT", ActionType::Retreat },
{ "retreat", ActionType::Retreat },
{ "撤退", ActionType::Retreat },
{ "SpeedUp", ActionType::SwitchSpeed },
{ "SPEEDUP", ActionType::SwitchSpeed },
{ "Speedup", ActionType::SwitchSpeed },
{ "speedup", ActionType::SwitchSpeed },
{ "二倍速", ActionType::SwitchSpeed },
{ "BulletTime", ActionType::BulletTime },
{ "BULLETTIME", ActionType::BulletTime },
{ "Bullettime", ActionType::BulletTime },
{ "bullettime", ActionType::BulletTime },
{ "子弹时间", ActionType::BulletTime },
{ "SkillUsage", ActionType::SkillUsage },
{ "SKILLUSAGE", ActionType::SkillUsage },
{ "Skillusage", ActionType::SkillUsage },
{ "skillusage", ActionType::SkillUsage },
{ "技能用法", ActionType::SkillUsage },
{ "Output", ActionType::Output },
{ "OUTPUT", ActionType::Output },
{ "output", ActionType::Output },
{ "输出", ActionType::Output },
{ "打印", ActionType::Output },
{ "SkillDaemon", ActionType::SkillDaemon },
{ "skilldaemon", ActionType::SkillDaemon },
{ "SKILLDAEMON", ActionType::SkillDaemon },
{ "Skilldaemon", ActionType::SkillDaemon },
{ "DoNothing", ActionType::SkillDaemon },
{ "摆完挂机", ActionType::SkillDaemon },
{ "开摆", ActionType::SkillDaemon },
{ "MoveCamera", ActionType::MoveCamera },
{ "movecamera", ActionType::MoveCamera },
{ "MOVECAMERA", ActionType::MoveCamera },
{ "Movecamera", ActionType::MoveCamera },
{ "移动镜头", ActionType::MoveCamera },
{ "DrawCard", ActionType::DrawCard },
{ "drawcard", ActionType::DrawCard },
{ "DRAWCARD", ActionType::DrawCard },
{ "Drawcard", ActionType::DrawCard },
{ "抽卡", ActionType::DrawCard },
{ "抽牌", ActionType::DrawCard },
{ "调配", ActionType::DrawCard },
{ "调配干员", ActionType::DrawCard },
{ "CheckIfStartOver", ActionType::CheckIfStartOver },
{ "Checkifstartover", ActionType::CheckIfStartOver },
{ "CHECKIFSTARTOVER", ActionType::CheckIfStartOver },
{ "checkifstartover", ActionType::CheckIfStartOver },
{ "检查重开", ActionType::CheckIfStartOver },
{ "ResetStopwatch", ActionType::ResetStopwatch },
{ "RESETSTOPWATCH", ActionType::ResetStopwatch },
{ "resetstopwatch", ActionType::ResetStopwatch },
{ "Resetstopwatch", ActionType::ResetStopwatch },
{ "重置全局计时器", ActionType::ResetStopwatch },
};
std::string type_str = action_info.get("type", "Deploy");
if (auto iter = ActionTypeMapping.find(type_str); iter != ActionTypeMapping.end()) {
action.type = iter->second;
}
else {
Log.warn("Unknown action type:", type_str);
continue;
}
action.kills = action_info.get("kills", 0);
action.cost_changes = action_info.get("cost_changes", 0);
action.costs = action_info.get("costs", 0);
action.cooling = action_info.get("cooling", -1);
action.name = action_info.get("name", std::string());
action.location.x = action_info.get("location", 0, 0);
action.location.y = action_info.get("location", 1, 0);
action.direction = string_to_direction(action_info.get("direction", "Right"));
action.modify_usage = static_cast<battle::SkillUsage>(action_info.get("skill_usage", 0));
action.modify_times = action_info.get("skill_times", 1);
action.pre_delay = action_info.get("pre_delay", 0);
auto post_delay_opt = action_info.find<int>("post_delay");
// 历史遗留字段,兼容一下
action.post_delay = post_delay_opt ? *post_delay_opt : action_info.get("rear_delay", 0);
action.time_out = action_info.get("timeout", INT_MAX);
action.doc = action_info.get("doc", std::string());
action.doc_color = action_info.get("doc_color", std::string());
if (action.type == ActionType::CheckIfStartOver) {
if (auto tool_men = action_info.find("tool_men")) {
action.role_counts = parse_role_counts(*tool_men);
}
}
else if (action.type == ActionType::MoveCamera) {
auto dist_arr = action_info.at("distance").as_array();
action.distance = std::make_pair(dist_arr[0].as_double(), dist_arr[1].as_double());
}
// ————————————————————————————————————————————————————————————————
// 实验性功能
// ————————————————————————————————————————————————————————————————
// 跳过使用未准备好的技能,主要用于关闭技能的场景
if (action.type == ActionType::UseSkill) {
action.skip_if_not_ready = action_info.get("skip_if_not_ready", false);
}
// 计时器
action.elapsed_time = action_info.get("elapsed_time", 0);
// ————————————————————————————————————————————————————————————————
actions_list.emplace_back(std::move(action));
}
return actions_list;
}
asst::battle::RoleCounts asst::CopilotConfig::parse_role_counts(const json::value& json)
{
battle::RoleCounts counts;
for (const auto& [role_name, count] : json.as_object()) {
auto role = get_role_type(role_name);
if (role == Role::Unknown) {
Log.error("Unknown role name: ", role_name);
throw std::runtime_error("Unknown role name: " + role_name);
}
counts.emplace(role, count.as_integer());
}
return counts;
}
asst::battle::DeployDirection asst::CopilotConfig::string_to_direction(const std::string& str)
{
// clang-format off
static const std::unordered_map<std::string, DeployDirection> DeployDirectionMapping = {
{ "Right", DeployDirection::Right }, { "RIGHT", DeployDirection::Right },
{ "right", DeployDirection::Right }, { "", DeployDirection::Right },
{ "Left", DeployDirection::Left }, { "LEFT", DeployDirection::Left },
{ "left", DeployDirection::Left }, { "", DeployDirection::Left },
{ "Up", DeployDirection::Up }, { "UP", DeployDirection::Up },
{ "up", DeployDirection::Up }, { "", DeployDirection::Up },
{ "Down", DeployDirection::Down }, { "DOWN", DeployDirection::Down },
{ "down", DeployDirection::Down }, { "", DeployDirection::Down },
{ "None", DeployDirection::None }, { "NONE", DeployDirection::None },
{ "none", DeployDirection::None }, { "", DeployDirection::None },
}; // clang-format on
if (auto iter = DeployDirectionMapping.find(str); iter != DeployDirectionMapping.end()) {
return iter->second;
}
else {
return DeployDirection::Right;
}
}