diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index 6236777431..1d9f29f72d 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -7,9 +7,13 @@ #include "AsstTypes.h" #include "Utils/NoWarningCVMat.h" -namespace asst +namespace asst::battle { - enum class BattleSkillUsage // 干员技能使用方法 + // 统一变量名: + // loc, location, 表示格子坐标,例如 [1, 1], [5, 5] + // pos, position, 表示像素坐标,例如 [1280, 720], [500, 300] + + enum class SkillUsage // 技能用法 { NotUse = 0, // 不自动使用 Possibly = 1, // 有就用,例如干员 棘刺 3 技能 @@ -17,20 +21,21 @@ namespace asst InTime = 3, // 自动判断使用时机,画饼.jpg OnceUsed }; - struct BattleDeployOper // 干员 + + struct OperUsage // 干员用法 { std::string name; int skill = 1; // 技能序号,取值范围 [1, 3] - BattleSkillUsage skill_usage = BattleSkillUsage::NotUse; - }; - struct BattleDeployInfo - { - Point loc; - Point pos; - BattleDeployOper info; + SkillUsage skill_usage = SkillUsage::NotUse; }; - enum class BattleDeployDirection + struct BattlefieldOper // 战场上的干员 + { + std::string name; + Point loc; + }; + + enum class DeployDirection { Right = 0, Down = 1, @@ -39,49 +44,7 @@ namespace asst None = 4 // 没有方向,通常是无人机之类的 }; - enum class BattleActionType // 操作类型 - { - Deploy, // 部署干员 - UseSkill, // 开技能 - Retreat, // 撤退干员 - SkillUsage, // 技能用法 - SwitchSpeed, // 切换二倍速 - BulletTime, // 使用 1/5 的速度 - UseAllSkill, // 使用所有技能,仅肉鸽模式 - Output, // 仅输出,什么都不操作,界面上也不显示 - SkillDaemon, // 什么都不做,有技能开技能,直到战斗结束 - }; - - struct BattleAction // 操作 - { - int kills = 0; - int costs = 0; - int cost_changes = 0; - int cooling = 0; - BattleActionType type = BattleActionType::Deploy; - std::string group_name; // 目标名,若 type >= SwitchSpeed, group_name 为空 - Point location; - BattleDeployDirection direction = BattleDeployDirection::Right; - BattleSkillUsage modify_usage = BattleSkillUsage::NotUse; - int pre_delay = 0; - int post_delay = 0; - int time_out = INT_MAX; - std::string doc; - std::string doc_color; - }; - - struct BattleCopilotData - { - std::string minimum_required; - std::string title; - std::string title_color; - std::string details; - std::string details_color; - std::unordered_map> groups; - std::vector actions; - }; - - enum class BattleRole + enum class Role { Unknown, Caster, @@ -95,64 +58,46 @@ namespace asst Drone }; - inline BattleRole get_role_type(const std::string& role_name) + inline static Role get_role_type(const std::string& role_name) { - static const std::unordered_map NameToRole = { - { "warrior", BattleRole::Warrior }, { "WARRIOR", BattleRole::Warrior }, - { "Warrior", BattleRole::Warrior }, { "近卫", BattleRole::Warrior }, + static const std::unordered_map NameToRole = { + { "warrior", Role::Warrior }, { "WARRIOR", Role::Warrior }, + { "Warrior", Role::Warrior }, { "近卫", Role::Warrior }, - { "pioneer", BattleRole::Pioneer }, { "PIONEER", BattleRole::Pioneer }, - { "Pioneer", BattleRole::Pioneer }, { "先锋", BattleRole::Pioneer }, + { "pioneer", Role::Pioneer }, { "PIONEER", Role::Pioneer }, + { "Pioneer", Role::Pioneer }, { "先锋", Role::Pioneer }, - { "medic", BattleRole::Medic }, { "MEDIC", BattleRole::Medic }, - { "Medic", BattleRole::Medic }, { "医疗", BattleRole::Medic }, + { "medic", Role::Medic }, { "MEDIC", Role::Medic }, + { "Medic", Role::Medic }, { "医疗", Role::Medic }, - { "tank", BattleRole::Tank }, { "TANK", BattleRole::Tank }, - { "Tank", BattleRole::Tank }, { "重装", BattleRole::Tank }, + { "tank", Role::Tank }, { "TANK", Role::Tank }, + { "Tank", Role::Tank }, { "重装", Role::Tank }, - { "sniper", BattleRole::Sniper }, { "SNIPER", BattleRole::Sniper }, - { "Sniper", BattleRole::Sniper }, { "狙击", BattleRole::Sniper }, + { "sniper", Role::Sniper }, { "SNIPER", Role::Sniper }, + { "Sniper", Role::Sniper }, { "狙击", Role::Sniper }, - { "caster", BattleRole::Caster }, { "CASTER", BattleRole::Caster }, - { "Caster", BattleRole::Caster }, { "术师", BattleRole::Caster }, + { "caster", Role::Caster }, { "CASTER", Role::Caster }, + { "Caster", Role::Caster }, { "术师", Role::Caster }, - { "support", BattleRole::Support }, { "SUPPORT", BattleRole::Support }, - { "Support", BattleRole::Support }, { "辅助", BattleRole::Support }, + { "support", Role::Support }, { "SUPPORT", Role::Support }, + { "Support", Role::Support }, { "辅助", Role::Support }, - { "special", BattleRole::Special }, { "SPECIAL", BattleRole::Special }, - { "Special", BattleRole::Special }, { "特种", BattleRole::Special }, + { "special", Role::Special }, { "SPECIAL", Role::Special }, + { "Special", Role::Special }, { "特种", Role::Special }, - { "drone", BattleRole::Drone }, { "DRONE", BattleRole::Drone }, - { "Drone", BattleRole::Drone }, { "无人机", BattleRole::Drone }, + { "drone", Role::Drone }, { "DRONE", Role::Drone }, + { "Drone", Role::Drone }, { "无人机", Role::Drone }, }; if (auto iter = NameToRole.find(role_name); iter != NameToRole.end()) { return iter->second; } - return BattleRole::Unknown; + return Role::Unknown; } -} // namespace asst -namespace std -{ - inline std::string to_string(const asst::BattleRole& role) - { - static const std::unordered_map RoleToName = { - { asst::BattleRole::Warrior, "Warrior" }, { asst::BattleRole::Pioneer, "Pioneer" }, - { asst::BattleRole::Medic, "Medic" }, { asst::BattleRole::Tank, "Tank" }, - { asst::BattleRole::Sniper, "Sniper" }, { asst::BattleRole::Caster, "Caster" }, - { asst::BattleRole::Support, "Support" }, { asst::BattleRole::Special, "Special" }, - { asst::BattleRole::Drone, "Drone" }, { asst::BattleRole::Unknown, "Unknown" } - }; - return RoleToName.at(role); - } -} - -namespace asst -{ - struct BattleRealTimeOper + struct DeploymentOper { int cost = 0; - BattleRole role = BattleRole::Unknown; + Role role = Role::Unknown; bool available = false; Rect rect; cv::Mat avatar; @@ -161,40 +106,14 @@ namespace asst bool cooling = false; }; - struct ReplacementHome - { - Point location; - BattleDeployDirection direction = BattleDeployDirection::Right; - }; - - struct ForceDeployDirection - { - BattleDeployDirection direction = BattleDeployDirection::Right; - std::unordered_set role = {}; - }; - - struct RoguelikeBattleData - { - std::string stage_name; - std::vector replacement_home; - std::unordered_set blacklist_location; - std::unordered_map force_deploy_direction; - std::vector key_kills; - std::array role_order = {}; - bool use_dice_stage = true; - int stop_deploy_blocking_num = INT_MAX; - int force_deploy_air_defense_num = 0; - bool force_ban_medic = false; - }; - - enum class BattleOperPosition + enum class OperPosition { None, Blocking, // 阻挡单位 AirDefense, // 对空单位 }; - enum class BattleLocationType + enum class LocationType { Invalid = -1, None = 0, @@ -203,48 +122,117 @@ namespace asst All = 3 }; - struct BattleCharData + struct OperProps { std::string name; - BattleRole role = BattleRole::Unknown; + Role role = Role::Unknown; std::array ranges; int rarity = 0; - bool with_direction = true; - BattleLocationType location_type = BattleLocationType::None; + LocationType location_type = LocationType::None; }; - struct BattleRecruitOperInfo + using AttackRange = std::vector; + + namespace copilot { - std::string name; - Rect rect; - int elite = 0; - int level = 0; - }; + enum class ActionType + { + Deploy, // 部署干员 + UseSkill, // 开技能 + Retreat, // 撤退干员 + SkillUsage, // 技能用法 + SwitchSpeed, // 切换二倍速 + BulletTime, // 使用 1/5 的速度 + Output, // 仅输出,什么都不操作,界面上也不显示 + SkillDaemon, // 什么都不做,有技能开技能,直到战斗结束 + }; - enum class RoguelikeSupportAnalyzeMode + struct Action + { + int kills = 0; + int costs = 0; + int cost_changes = 0; + int cooling = 0; + ActionType type = ActionType::Deploy; + std::string group_name; // 目标名,若 type >= SwitchSpeed, group_name 为空 + Point location; + DeployDirection direction = DeployDirection::Right; + SkillUsage modify_usage = SkillUsage::NotUse; + int pre_delay = 0; + int post_delay = 0; + int time_out = INT_MAX; // TODO + std::string doc; + std::string doc_color; + }; + + struct CombatData // 作业 JSON 数据 + { + std::string minimum_required; + std::string title; + std::string title_color; + std::string details; + std::string details_color; + std::unordered_map> groups; + std::vector actions; + }; + } + + namespace roguelike { - ChooseSupportBtn, - AnalyzeChars, - RefreshSupportBtn - }; + struct ReplacementHome + { + Point location; + DeployDirection direction = DeployDirection::Right; + }; - struct RoguelikeRecruitSupportCharInfo - { - BattleRecruitOperInfo oper_info; - bool is_friend = false; // 是否为好友助战 - int max_elite = 0; // 两次招募后的实际精英化与等级 - int max_level = 0; - }; + struct ForceDeployDirection + { + DeployDirection direction = DeployDirection::Right; + std::unordered_set role = {}; + }; - struct RoguelikeRefreshSupportInfo - { - Rect rect; - bool in_cooldown = false; - int remain_secs = 0; // 刷新冷却时间 - }; + struct CombatData + { + std::string stage_name; + std::vector replacement_home; + std::unordered_set blacklist_location; + std::unordered_map force_deploy_direction; + std::vector key_kills; + std::array role_order = {}; + bool use_dice_stage = true; + int stop_deploy_blocking_num = INT_MAX; + int force_deploy_air_defense_num = 0; + bool force_ban_medic = false; + }; - using BattleAttackRange = std::vector; + struct Recruitment + { + std::string name; + Rect rect; + int elite = 0; + int level = 0; + }; - static constexpr std::string_view RoguelikePhantomThemeName = "Phantom"; - static constexpr std::string_view RoguelikeMizukiThemeName = "Mizuki"; -} // namespace asst + enum class SupportAnalyzeMode + { + ChooseSupportBtn, + AnalyzeChars, + RefreshSupportBtn + }; + + struct RecruitSupportCharInfo + { + Recruitment oper_info; + bool is_friend = false; // 是否为好友助战 + int max_elite = 0; // 两次招募后的实际精英化与等级 + int max_level = 0; + }; + + struct RefreshSupportInfo + { + Rect rect; + bool in_cooldown = false; + int remain_secs = 0; // 刷新冷却时间 + }; + } // namespace roguelike +} // namespace asst::battle diff --git a/src/MaaCore/Common/AsstTypes.h b/src/MaaCore/Common/AsstTypes.h index 6c3e22fc0e..2caccbacc2 100644 --- a/src/MaaCore/Common/AsstTypes.h +++ b/src/MaaCore/Common/AsstTypes.h @@ -60,6 +60,8 @@ namespace asst static constexpr Point left() { return { -1, 0 }; } static constexpr Point up() { return { 0, -1 }; } static constexpr Point zero() { return { 0, 0 }; } + bool empty() const noexcept { return x == 0 && y == 0; } + int x = 0; int y = 0; @@ -408,4 +410,6 @@ namespace asst }; inline static const std::string UploadDataSource = "MeoAssistant"; + inline static constexpr std::string_view RoguelikePhantomThemeName = "Phantom"; + inline static constexpr std::string_view RoguelikeMizukiThemeName = "Mizuki"; } // namespace asst diff --git a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp index c85754c96f..46fd8f356b 100644 --- a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp @@ -8,17 +8,18 @@ bool asst::BattleDataConfig::parse(const json::value& json) { for (const auto& char_data_json : json.at("chars").as_object() | views::values) { - BattleCharData data; + battle::OperProps data; std::string name = char_data_json.at("name").as_string(); data.name = name; - static const std::unordered_map RoleMap = { - { "CASTER", BattleRole::Caster }, { "MEDIC", BattleRole::Medic }, { "PIONEER", BattleRole::Pioneer }, - { "SNIPER", BattleRole::Sniper }, { "SPECIAL", BattleRole::Special }, { "SUPPORT", BattleRole::Support }, - { "TANK", BattleRole::Tank }, { "WARRIOR", BattleRole::Warrior }, + static const std::unordered_map RoleMap = { + { "CASTER", battle::Role::Caster }, { "MEDIC", battle::Role::Medic }, + { "PIONEER", battle::Role::Pioneer }, { "SNIPER", battle::Role::Sniper }, + { "SPECIAL", battle::Role::Special }, { "SUPPORT", battle::Role::Support }, + { "TANK", battle::Role::Tank }, { "WARRIOR", battle::Role::Warrior }, }; if (auto iter = RoleMap.find(char_data_json.at("profession").as_string()); iter == RoleMap.cend()) { - data.role = BattleRole::Drone; + data.role = battle::Role::Drone; } else { data.role = iter->second; @@ -29,15 +30,15 @@ bool asst::BattleDataConfig::parse(const json::value& json) data.ranges.at(i) = ranges_json.at(i).as_string(); } - static const std::unordered_map PositionMap = { - { "NONE", BattleLocationType::All }, // 这种很多都是道具之类的,一般哪都能放 - { "MELEE", BattleLocationType::Melee }, - { "RANGED", BattleLocationType::Ranged }, - { "ALL", BattleLocationType::All }, + static const std::unordered_map PositionMap = { + { "NONE", battle::LocationType::All }, // 这种很多都是道具之类的,一般哪都能放 + { "MELEE", battle::LocationType::Melee }, + { "RANGED", battle::LocationType::Ranged }, + { "ALL", battle::LocationType::All }, }; if (auto iter = PositionMap.find(char_data_json.at("position").as_string()); iter == PositionMap.cend()) { Log.warn("Unknown position", char_data_json.at("position").as_string()); - data.location_type = BattleLocationType::Invalid; + data.location_type = battle::LocationType::Invalid; } else { data.location_type = iter->second; @@ -49,7 +50,7 @@ bool asst::BattleDataConfig::parse(const json::value& json) m_chars.emplace(std::move(name), std::move(data)); } for (const auto& [id, points_json] : json.at("ranges").as_object()) { - BattleAttackRange points; + battle::AttackRange points; for (const auto& point : points_json.as_array()) { points.emplace_back(point[0].as_integer(), point[1].as_integer()); } diff --git a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h index a9e7bf3d04..9ae2ca6609 100644 --- a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h +++ b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h @@ -13,11 +13,11 @@ namespace asst public: virtual ~BattleDataConfig() override = default; - BattleRole get_role(const std::string& name) const + battle::Role get_role(const std::string& name) const { auto iter = m_chars.find(name); if (iter == m_chars.cend()) { - return BattleRole::Unknown; + return battle::Role::Unknown; } return iter->second.role; } @@ -31,18 +31,18 @@ namespace asst return iter->second.rarity; } - BattleLocationType get_location_type(const std::string& name) const + battle::LocationType get_location_type(const std::string& name) const { auto iter = m_chars.find(name); if (iter == m_chars.cend()) { - return BattleLocationType::Invalid; + return battle::LocationType::Invalid; } return iter->second.location_type; } - static inline const BattleAttackRange& EmptyRange { { 0, 0 } }; + static inline const battle::AttackRange& EmptyRange { { 0, 0 } }; - const BattleAttackRange& get_range(const std::string& name, size_t index) const + const battle::AttackRange& get_range(const std::string& name, size_t index) const { auto char_iter = m_chars.find(name); if (char_iter == m_chars.cend()) { @@ -68,8 +68,8 @@ namespace asst virtual bool parse(const json::value& json) override; private: - std::unordered_map m_chars; - std::unordered_map m_ranges; + std::unordered_map m_chars; + std::unordered_map m_ranges; }; inline static auto& BattleData = BattleDataConfig::get_instance(); } // namespace asst diff --git a/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp b/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp index 030dac7966..89f4c06bb7 100644 --- a/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/CopilotConfig.cpp @@ -5,6 +5,9 @@ #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)(); @@ -26,12 +29,12 @@ bool asst::CopilotConfig::parse(const json::value& json) if (auto opt = json.find("groups")) { for (const auto& group_info : opt.value()) { std::string group_name = group_info.at("name").as_string(); - std::vector oper_vec; + std::vector oper_vec; for (const auto& oper_info : group_info.at("opers").as_array()) { - BattleDeployOper oper; + OperUsage oper; oper.name = oper_info.at("name").as_string(); oper.skill = oper_info.get("skill", 1); - oper.skill_usage = static_cast(oper_info.get("skill_usage", 0)); + oper.skill_usage = static_cast(oper_info.get("skill_usage", 0)); oper_vec.emplace_back(std::move(oper)); } m_data.groups.emplace(std::move(group_name), std::move(oper_vec)); @@ -40,10 +43,10 @@ bool asst::CopilotConfig::parse(const json::value& json) if (auto opt = json.find("opers")) { for (const auto& oper_info : opt.value()) { - BattleDeployOper oper; + OperUsage oper; oper.name = oper_info.at("name").as_string(); oper.skill = oper_info.get("skill", 1); - oper.skill_usage = static_cast(oper_info.get("skill_usage", 0)); + oper.skill_usage = static_cast(oper_info.get("skill_usage", 0)); // 单个干员的,干员名直接作为组名 std::string group_name = oper.name; @@ -52,54 +55,54 @@ bool asst::CopilotConfig::parse(const json::value& json) } for (const auto& action_info : json.at("actions").as_array()) { - BattleAction action; - static const std::unordered_map ActionTypeMapping = { - { "Deploy", BattleActionType::Deploy }, - { "DEPLOY", BattleActionType::Deploy }, - { "deploy", BattleActionType::Deploy }, - { "部署", BattleActionType::Deploy }, + Action action; + static const std::unordered_map ActionTypeMapping = { + { "Deploy", ActionType::Deploy }, + { "DEPLOY", ActionType::Deploy }, + { "deploy", ActionType::Deploy }, + { "部署", ActionType::Deploy }, - { "Skill", BattleActionType::UseSkill }, - { "SKILL", BattleActionType::UseSkill }, - { "skill", BattleActionType::UseSkill }, - { "技能", BattleActionType::UseSkill }, + { "Skill", ActionType::UseSkill }, + { "SKILL", ActionType::UseSkill }, + { "skill", ActionType::UseSkill }, + { "技能", ActionType::UseSkill }, - { "Retreat", BattleActionType::Retreat }, - { "RETREAT", BattleActionType::Retreat }, - { "retreat", BattleActionType::Retreat }, - { "撤退", BattleActionType::Retreat }, + { "Retreat", ActionType::Retreat }, + { "RETREAT", ActionType::Retreat }, + { "retreat", ActionType::Retreat }, + { "撤退", ActionType::Retreat }, - { "SpeedUp", BattleActionType::SwitchSpeed }, - { "SPEEDUP", BattleActionType::SwitchSpeed }, - { "Speedup", BattleActionType::SwitchSpeed }, - { "speedup", BattleActionType::SwitchSpeed }, - { "二倍速", BattleActionType::SwitchSpeed }, + { "SpeedUp", ActionType::SwitchSpeed }, + { "SPEEDUP", ActionType::SwitchSpeed }, + { "Speedup", ActionType::SwitchSpeed }, + { "speedup", ActionType::SwitchSpeed }, + { "二倍速", ActionType::SwitchSpeed }, - { "BulletTime", BattleActionType::BulletTime }, - { "BULLETTIME", BattleActionType::BulletTime }, - { "Bullettime", BattleActionType::BulletTime }, - { "bullettime", BattleActionType::BulletTime }, - { "子弹时间", BattleActionType::BulletTime }, + { "BulletTime", ActionType::BulletTime }, + { "BULLETTIME", ActionType::BulletTime }, + { "Bullettime", ActionType::BulletTime }, + { "bullettime", ActionType::BulletTime }, + { "子弹时间", ActionType::BulletTime }, - { "SkillUsage", BattleActionType::SkillUsage }, - { "SKILLUSAGE", BattleActionType::SkillUsage }, - { "Skillusage", BattleActionType::SkillUsage }, - { "skillusage", BattleActionType::SkillUsage }, - { "技能用法", BattleActionType::SkillUsage }, + { "SkillUsage", ActionType::SkillUsage }, + { "SKILLUSAGE", ActionType::SkillUsage }, + { "Skillusage", ActionType::SkillUsage }, + { "skillusage", ActionType::SkillUsage }, + { "技能用法", ActionType::SkillUsage }, - { "Output", BattleActionType::Output }, - { "OUTPUT", BattleActionType::Output }, - { "output", BattleActionType::Output }, - { "输出", BattleActionType::Output }, - { "打印", BattleActionType::Output }, + { "Output", ActionType::Output }, + { "OUTPUT", ActionType::Output }, + { "output", ActionType::Output }, + { "输出", ActionType::Output }, + { "打印", ActionType::Output }, - { "SkillDaemon", BattleActionType::SkillDaemon }, - { "skilldaemon", BattleActionType::SkillDaemon }, - { "SKILLDAEMON", BattleActionType::SkillDaemon }, - { "Skilldaemon", BattleActionType::SkillDaemon }, - { "DoNothing", BattleActionType::SkillDaemon }, - { "摆完挂机", BattleActionType::SkillDaemon }, - { "开摆", BattleActionType::SkillDaemon }, + { "SkillDaemon", ActionType::SkillDaemon }, + { "skilldaemon", ActionType::SkillDaemon }, + { "SKILLDAEMON", ActionType::SkillDaemon }, + { "Skilldaemon", ActionType::SkillDaemon }, + { "DoNothing", ActionType::SkillDaemon }, + { "摆完挂机", ActionType::SkillDaemon }, + { "开摆", ActionType::SkillDaemon }, }; std::string type_str = action_info.get("type", "Deploy"); @@ -108,7 +111,7 @@ bool asst::CopilotConfig::parse(const json::value& json) action.type = iter->second; } else { - action.type = BattleActionType::Deploy; + action.type = ActionType::Deploy; } action.kills = action_info.get("kills", 0); action.cost_changes = action_info.get("cost_changes", 0); @@ -119,21 +122,21 @@ bool asst::CopilotConfig::parse(const json::value& json) action.location.x = action_info.get("location", 0, 0); action.location.y = action_info.get("location", 1, 0); - static const std::unordered_map DeployDirectionMapping = { - { "Right", BattleDeployDirection::Right }, { "RIGHT", BattleDeployDirection::Right }, - { "right", BattleDeployDirection::Right }, { "右", BattleDeployDirection::Right }, + static const std::unordered_map DeployDirectionMapping = { + { "Right", DeployDirection::Right }, { "RIGHT", DeployDirection::Right }, + { "right", DeployDirection::Right }, { "右", DeployDirection::Right }, - { "Left", BattleDeployDirection::Left }, { "LEFT", BattleDeployDirection::Left }, - { "left", BattleDeployDirection::Left }, { "左", BattleDeployDirection::Left }, + { "Left", DeployDirection::Left }, { "LEFT", DeployDirection::Left }, + { "left", DeployDirection::Left }, { "左", DeployDirection::Left }, - { "Up", BattleDeployDirection::Up }, { "UP", BattleDeployDirection::Up }, - { "up", BattleDeployDirection::Up }, { "上", BattleDeployDirection::Up }, + { "Up", DeployDirection::Up }, { "UP", DeployDirection::Up }, + { "up", DeployDirection::Up }, { "上", DeployDirection::Up }, - { "Down", BattleDeployDirection::Down }, { "DOWN", BattleDeployDirection::Down }, - { "down", BattleDeployDirection::Down }, { "下", BattleDeployDirection::Down }, + { "Down", DeployDirection::Down }, { "DOWN", DeployDirection::Down }, + { "down", DeployDirection::Down }, { "下", DeployDirection::Down }, - { "None", BattleDeployDirection::None }, { "NONE", BattleDeployDirection::None }, - { "none", BattleDeployDirection::None }, { "无", BattleDeployDirection::None }, + { "None", DeployDirection::None }, { "NONE", DeployDirection::None }, + { "none", DeployDirection::None }, { "无", DeployDirection::None }, }; std::string direction_str = action_info.get("direction", "Right"); @@ -142,9 +145,9 @@ bool asst::CopilotConfig::parse(const json::value& json) action.direction = iter->second; } else { - action.direction = BattleDeployDirection::Right; + action.direction = DeployDirection::Right; } - action.modify_usage = static_cast(action_info.get("skill_usage", 0)); + action.modify_usage = static_cast(action_info.get("skill_usage", 0)); action.pre_delay = action_info.get("pre_delay", 0); auto post_delay_opt = action_info.find("post_delay"); // 历史遗留字段,兼容一下 diff --git a/src/MaaCore/Config/Miscellaneous/CopilotConfig.h b/src/MaaCore/Config/Miscellaneous/CopilotConfig.h index 688031b174..bc7798a412 100644 --- a/src/MaaCore/Config/Miscellaneous/CopilotConfig.h +++ b/src/MaaCore/Config/Miscellaneous/CopilotConfig.h @@ -9,14 +9,14 @@ namespace asst public: virtual ~CopilotConfig() override = default; - const BattleCopilotData& get_data() const noexcept { return m_data; } + const battle::copilot::CombatData& get_data() const noexcept { return m_data; } const std::string& get_stage_name() const noexcept { return m_stage_name; } void clear(); protected: virtual bool parse(const json::value& json) override; - BattleCopilotData m_data; + battle::copilot::CombatData m_data; std::string m_stage_name; }; diff --git a/src/MaaCore/Config/Miscellaneous/RecruitConfig.cpp b/src/MaaCore/Config/Miscellaneous/RecruitConfig.cpp index 23f93815f9..bb0978f311 100644 --- a/src/MaaCore/Config/Miscellaneous/RecruitConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/RecruitConfig.cpp @@ -20,7 +20,7 @@ bool asst::RecruitConfig::parse(const json::value& json) clear(); for (const json::value& oper : json.at("operators").as_array()) { - RecruitOperInfo oper_temp; + Recruitment oper_temp; oper_temp.name = oper.at("name").as_string(); oper_temp.level = oper.at("rarity").as_integer(); @@ -36,7 +36,7 @@ bool asst::RecruitConfig::parse(const json::value& json) } // 按干员等级排个序 - ranges::sort(m_all_opers, std::greater {}, std::mem_fn(&RecruitOperInfo::level)); + ranges::sort(m_all_opers, std::greater {}, std::mem_fn(&Recruitment::level)); return true; } diff --git a/src/MaaCore/Config/Miscellaneous/RecruitConfig.h b/src/MaaCore/Config/Miscellaneous/RecruitConfig.h index 97b22d4b36..96d8940837 100644 --- a/src/MaaCore/Config/Miscellaneous/RecruitConfig.h +++ b/src/MaaCore/Config/Miscellaneous/RecruitConfig.h @@ -14,7 +14,7 @@ namespace asst { // 干员信息,公开招募相关 - struct RecruitOperInfo + struct Recruitment { std::string name; int level = 0; @@ -22,7 +22,7 @@ namespace asst bool has_tag(const std::string& tag) const { return tags.contains(tag); } - friend std::strong_ordering operator<=>(const RecruitOperInfo& lhs, const RecruitOperInfo& rhs) + friend std::strong_ordering operator<=>(const Recruitment& lhs, const Recruitment& rhs) { if (lhs.level != rhs.level) return lhs.level <=> rhs.level; // increment order #ifdef __clang__ @@ -37,7 +37,7 @@ namespace asst #endif } - friend bool operator==(const RecruitOperInfo& lhs, const RecruitOperInfo& rhs) + friend bool operator==(const Recruitment& lhs, const Recruitment& rhs) { return lhs.name == rhs.name && lhs.level == rhs.level; } @@ -48,7 +48,7 @@ namespace asst { // TODO: using vector here can be expensive std::vector tags; - std::vector opers; + std::vector opers; int max_level = 0; int min_level = 0; double avg_level = 0; @@ -57,14 +57,14 @@ namespace asst { min_level = std::transform_reduce( opers.cbegin(), opers.cend(), 7, [](int a, int b) -> int { return (std::min)(a, b); }, - std::mem_fn(&RecruitOperInfo::level)); + std::mem_fn(&Recruitment::level)); max_level = std::transform_reduce( opers.cbegin(), opers.cend(), 0, [](int a, int b) -> int { return (std::max)(a, b); }, - std::mem_fn(&RecruitOperInfo::level)); + std::mem_fn(&Recruitment::level)); avg_level = std::transform_reduce(opers.cbegin(), opers.cend(), 0., std::plus {}, - std::mem_fn(&RecruitOperInfo::level)) / + std::mem_fn(&Recruitment::level)) / static_cast(opers.size()); } @@ -98,7 +98,7 @@ namespace asst static constexpr int CorrectNumberOfTags = 5; const std::unordered_set& get_all_tags() const noexcept { return m_all_tags; } - const std::vector& get_all_opers() const noexcept { return m_all_opers; } + const std::vector& get_all_opers() const noexcept { return m_all_opers; } std::string get_tag_name(const TagId& id) const noexcept; protected: @@ -107,7 +107,7 @@ namespace asst void clear(); std::unordered_set m_all_tags; - std::vector m_all_opers; + std::vector m_all_opers; std::unordered_map m_all_tags_name; }; inline static auto& RecruitData = RecruitConfig::get_instance(); diff --git a/src/MaaCore/Config/Miscellaneous/TilePack.cpp b/src/MaaCore/Config/Miscellaneous/TilePack.cpp index 5ac1c81e27..a519b4273f 100644 --- a/src/MaaCore/Config/Miscellaneous/TilePack.cpp +++ b/src/MaaCore/Config/Miscellaneous/TilePack.cpp @@ -97,7 +97,7 @@ std::unordered_map proc_data(const std::v } Point loc(static_cast(x), static_cast(y)); - dst.emplace(loc, TileInfo { static_cast(tile.buildableType), + dst.emplace(loc, TileInfo { static_cast(tile.buildableType), static_cast(tile.heightType), key, Point(static_cast(cv_p.x), static_cast(cv_p.y)), loc }); } diff --git a/src/MaaCore/Config/Miscellaneous/TilePack.h b/src/MaaCore/Config/Miscellaneous/TilePack.h index bbeda7faf0..68cfac49a9 100644 --- a/src/MaaCore/Config/Miscellaneous/TilePack.h +++ b/src/MaaCore/Config/Miscellaneous/TilePack.h @@ -50,7 +50,7 @@ namespace asst struct TileInfo { - BattleLocationType buildable = BattleLocationType::Invalid; + battle::LocationType buildable = battle::LocationType::Invalid; HeightType height = HeightType::Invalid; TileKey key = TileKey::Invalid; Point pos; // 像素坐标 diff --git a/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp b/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp index 7ca2ae58bd..1bb9555843 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp +++ b/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.cpp @@ -4,8 +4,10 @@ #include "Utils/Logger.hpp" -std::optional asst::RoguelikeCopilotConfig::get_stage_data( - const std::string& stage_name) const +using namespace asst::battle; +using namespace asst::battle::roguelike; + +std::optional asst::RoguelikeCopilotConfig::get_stage_data(const std::string& stage_name) const { auto it = m_stage_data.find(stage_name); if (it == m_stage_data.end()) { @@ -18,23 +20,23 @@ bool asst::RoguelikeCopilotConfig::parse(const json::value& json) { for (const auto& stage_info : json.as_array()) { std::string stage_name = stage_info.at("stage_name").as_string(); - RoguelikeBattleData data; + CombatData data; data.stage_name = stage_name; - static const std::unordered_map DeployDirectionMapping = { - { "Right", BattleDeployDirection::Right }, { "RIGHT", BattleDeployDirection::Right }, - { "right", BattleDeployDirection::Right }, { "右", BattleDeployDirection::Right }, + static const std::unordered_map DeployDirectionMapping = { + { "Right", DeployDirection::Right }, { "RIGHT", DeployDirection::Right }, + { "right", DeployDirection::Right }, { "右", DeployDirection::Right }, - { "Left", BattleDeployDirection::Left }, { "LEFT", BattleDeployDirection::Left }, - { "left", BattleDeployDirection::Left }, { "左", BattleDeployDirection::Left }, + { "Left", DeployDirection::Left }, { "LEFT", DeployDirection::Left }, + { "left", DeployDirection::Left }, { "左", DeployDirection::Left }, - { "Up", BattleDeployDirection::Up }, { "UP", BattleDeployDirection::Up }, - { "up", BattleDeployDirection::Up }, { "上", BattleDeployDirection::Up }, + { "Up", DeployDirection::Up }, { "UP", DeployDirection::Up }, + { "up", DeployDirection::Up }, { "上", DeployDirection::Up }, - { "Down", BattleDeployDirection::Down }, { "DOWN", BattleDeployDirection::Down }, - { "down", BattleDeployDirection::Down }, { "下", BattleDeployDirection::Down }, + { "Down", DeployDirection::Down }, { "DOWN", DeployDirection::Down }, + { "down", DeployDirection::Down }, { "下", DeployDirection::Down }, - { "None", BattleDeployDirection::None }, { "NONE", BattleDeployDirection::None }, - { "none", BattleDeployDirection::None }, { "无", BattleDeployDirection::None }, + { "None", DeployDirection::None }, { "NONE", DeployDirection::None }, + { "none", DeployDirection::None }, { "无", DeployDirection::None }, }; if (auto opt = stage_info.find("replacement_home")) { for (auto& point : opt.value()) { @@ -45,7 +47,7 @@ bool asst::RoguelikeCopilotConfig::parse(const json::value& json) home.direction = iter->second; } else { - home.direction = BattleDeployDirection::None; + home.direction = DeployDirection::None; } data.replacement_home.emplace_back(std::move(home)); } @@ -77,19 +79,19 @@ bool asst::RoguelikeCopilotConfig::parse(const json::value& json) } constexpr int RoleNumber = 9; - static constexpr std::array RoleOrder = { - BattleRole::Warrior, BattleRole::Pioneer, BattleRole::Medic, BattleRole::Tank, BattleRole::Sniper, - BattleRole::Caster, BattleRole::Support, BattleRole::Special, BattleRole::Drone, + static constexpr std::array RoleOrder = { + Role::Warrior, Role::Pioneer, Role::Medic, Role::Tank, Role::Sniper, + Role::Caster, Role::Support, Role::Special, Role::Drone, }; if (auto opt = stage_info.find("role_order")) { const auto& raw_roles = opt.value(); using views::filter, views::transform; - std::unordered_set specified_role; - std::vector role_order; + std::unordered_set specified_role; + std::vector role_order; bool is_legal = true; if (ranges::find_if_not(raw_roles | views::all, std::mem_fn(&json::value::is_string)) != raw_roles.end()) { - Log.error("BattleRole should be string"); + Log.error("Role should be string"); return false; } auto roles = raw_roles | filter(&json::value::is_string) | transform(&json::value::as_string) | @@ -99,13 +101,13 @@ bool asst::RoguelikeCopilotConfig::parse(const json::value& json) }); for (const std::string& role_name : roles) { const auto role = get_role_type(role_name); - if (role == BattleRole::Unknown) [[unlikely]] { - Log.error("Unknown BattleRole:", role_name); + if (role == Role::Unknown) [[unlikely]] { + Log.error("Unknown Role:", role_name); is_legal = false; break; } if (specified_role.contains(role)) [[unlikely]] { - Log.error("Duplicated BattleRole:", role_name); + Log.error("Duplicated Role:", role_name); is_legal = false; break; } @@ -113,7 +115,7 @@ bool asst::RoguelikeCopilotConfig::parse(const json::value& json) role_order.emplace_back(role); } if (is_legal) [[likely]] { - ranges::copy(RoleOrder | filter([&](BattleRole role) { return !specified_role.contains(role); }), + ranges::copy(RoleOrder | filter([&](Role role) { return !specified_role.contains(role); }), std::back_inserter(role_order)); if (role_order.size() != RoleNumber) [[unlikely]] { Log.error("Unexpected role_order size:", role_order.size()); @@ -139,16 +141,16 @@ bool asst::RoguelikeCopilotConfig::parse(const json::value& json) fd_dir.direction = iter->second; } else { - fd_dir.direction = BattleDeployDirection::None; + fd_dir.direction = DeployDirection::None; } - if (fd_dir.direction == BattleDeployDirection::None) [[unlikely]] { + if (fd_dir.direction == DeployDirection::None) [[unlikely]] { Log.error("Unknown direction"); return false; } - std::unordered_set fd_role; + std::unordered_set fd_role; for (auto& role_name : point["role"].as_array()) { const auto role = get_role_type(role_name.as_string()); - if (role == BattleRole::Unknown) [[unlikely]] { + if (role == Role::Unknown) [[unlikely]] { Log.error("Unknown role name:", role_name); return false; } diff --git a/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.h b/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.h index 5336efcaaf..af66daae86 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.h +++ b/src/MaaCore/Config/Roguelike/RoguelikeCopilotConfig.h @@ -12,11 +12,11 @@ namespace asst public: virtual ~RoguelikeCopilotConfig() override = default; - std::optional get_stage_data(const std::string& stage_name) const; + std::optional get_stage_data(const std::string& stage_name) const; protected: virtual bool parse(const json::value& json) override; - std::unordered_map m_stage_data; + std::unordered_map m_stage_data; }; inline static auto& RoguelikeCopilot = RoguelikeCopilotConfig::get_instance(); diff --git a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp index 201288d15a..f41cd0fe90 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp +++ b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.cpp @@ -18,9 +18,9 @@ const asst::RoguelikeOperInfo& asst::RoguelikeRecruitConfig::get_oper_info(const } const std::vector> asst::RoguelikeRecruitConfig::get_role_info( - const std::string& theme, const BattleRole& role) const noexcept + const std::string& theme, const battle::Role& role) const noexcept { - if (role == BattleRole::Unknown) { + if (role == battle::Role::Unknown) { return std::vector>(); } auto& map = m_role_offset_map.at(theme); @@ -47,7 +47,7 @@ bool asst::RoguelikeRecruitConfig::parse(const json::value& json) role_offset.emplace_back(offset_pair); } } - m_role_offset_map[theme].emplace(get_role_type(str_role), std::move(role_offset)); + m_role_offset_map[theme].emplace(battle::get_role_type(str_role), std::move(role_offset)); for (const auto& oper_info : role_json.at("opers").as_array()) { std::string name = oper_info.at("name").as_string(); RoguelikeOperInfo info; @@ -61,8 +61,8 @@ bool asst::RoguelikeRecruitConfig::parse(const json::value& json) info.is_alternate = oper_info.get("is_alternate", false); info.skill = oper_info.at("skill").as_integer(); info.alternate_skill = oper_info.get("alternate_skill", 0); - info.skill_usage = static_cast(oper_info.get("skill_usage", 1)); - info.alternate_skill_usage = static_cast(oper_info.get("alternate_skill_usage", 1)); + info.skill_usage = static_cast(oper_info.get("skill_usage", 1)); + info.alternate_skill_usage = static_cast(oper_info.get("alternate_skill_usage", 1)); if (auto opt = oper_info.find("recruit_priority_offset")) { for (const auto& offset : opt.value()) { std::pair offset_pair = diff --git a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h index 0bc3a430f3..34bce575e2 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h +++ b/src/MaaCore/Config/Roguelike/RoguelikeRecruitConfig.h @@ -24,8 +24,8 @@ namespace asst bool is_alternate = false; // 是否后备干员 (允许重复招募、划到后备干员时不再往右划动) int skill = 0; int alternate_skill = 0; - BattleSkillUsage skill_usage = BattleSkillUsage::Possibly; - BattleSkillUsage alternate_skill_usage = BattleSkillUsage::Possibly; + battle::SkillUsage skill_usage = battle::SkillUsage::Possibly; + battle::SkillUsage alternate_skill_usage = battle::SkillUsage::Possibly; }; class RoguelikeRecruitConfig final : public SingletonHolder, public AbstractConfig @@ -35,7 +35,7 @@ namespace asst const RoguelikeOperInfo& get_oper_info(const std::string& theme, const std::string& name) const noexcept; const std::vector> get_role_info(const std::string& theme, - const BattleRole& role) const noexcept; + const battle::Role& role) const noexcept; protected: virtual bool parse(const json::value& json) override; @@ -43,7 +43,7 @@ namespace asst void clear(); std::unordered_map> m_all_opers; - std::unordered_map>>> + std::unordered_map>>> m_role_offset_map; }; diff --git a/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.cpp b/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.cpp index 6e590365a9..ac6d55d787 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.cpp +++ b/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.cpp @@ -12,14 +12,14 @@ bool asst::RoguelikeShoppingConfig::parse(const json::value& json) for (const auto& goods_json : theme_json.as_array()) { std::string name = goods_json.at("name").as_string(); - std::vector roles; + std::vector roles; if (auto roles_opt = goods_json.find("roles")) { for (const auto& role_json : roles_opt.value()) { - static const std::unordered_map RoleMap = { - { "CASTER", BattleRole::Caster }, { "MEDIC", BattleRole::Medic }, - { "PIONEER", BattleRole::Pioneer }, { "SNIPER", BattleRole::Sniper }, - { "SPECIAL", BattleRole::Special }, { "SUPPORT", BattleRole::Support }, - { "TANK", BattleRole::Tank }, { "WARRIOR", BattleRole::Warrior }, + static const std::unordered_map RoleMap = { + { "CASTER", battle::Role::Caster }, { "MEDIC", battle::Role::Medic }, + { "PIONEER", battle::Role::Pioneer }, { "SNIPER", battle::Role::Sniper }, + { "SPECIAL", battle::Role::Special }, { "SUPPORT", battle::Role::Support }, + { "TANK", battle::Role::Tank }, { "WARRIOR", battle::Role::Warrior }, }; roles.emplace_back(RoleMap.at(role_json.as_string())); } diff --git a/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.h b/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.h index 07f6297493..6756aab9ba 100644 --- a/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.h +++ b/src/MaaCore/Config/Roguelike/RoguelikeShoppingConfig.h @@ -11,7 +11,7 @@ namespace asst struct RoguelikeGoods { std::string name; - std::vector roles; + std::vector roles; std::vector chars; int promotion = 0; // 晋升 N 个干员 bool no_longer_buy = false; diff --git a/src/MaaCore/Controller.cpp b/src/MaaCore/Controller.cpp index c6e41f9790..5c00a52d85 100644 --- a/src/MaaCore/Controller.cpp +++ b/src/MaaCore/Controller.cpp @@ -396,8 +396,7 @@ std::optional asst::Controller::call_command(const std::string& cmd reconnect_info["details"]["times"] = i; callback(AsstMsg::ConnectionInfo, reconnect_info); - // TODO: 也许 WIN32 可以用 WaitForSingleObjectEx 做一个允许外部打断的 sleep - std::this_thread::sleep_for(10s); + sleep(10 * 1000); if (need_exit()) { break; } @@ -774,10 +773,7 @@ void asst::Controller::random_delay() const static std::default_random_engine rand_engine(std::random_device {}()); static std::uniform_int_distribution rand_uni(opt.control_delay_lower, opt.control_delay_upper); - unsigned rand_delay = rand_uni(rand_engine); - - Log.trace("random_delay |", rand_delay, "ms"); - std::this_thread::sleep_for(std::chrono::milliseconds(rand_delay)); + sleep(rand_uni(rand_engine)); } } diff --git a/src/MaaCore/InstHelper.cpp b/src/MaaCore/InstHelper.cpp index 5bfd5b642f..9c0440e580 100644 --- a/src/MaaCore/InstHelper.cpp +++ b/src/MaaCore/InstHelper.cpp @@ -1,6 +1,7 @@ #include "InstHelper.h" #include "Assistant.h" +#include "Utils/Logger.hpp" asst::InstHelper::InstHelper(asst::Assistant* inst) : m_inst(inst) {} @@ -15,4 +16,29 @@ std::shared_ptr asst::InstHelper::status() const bool asst::InstHelper::need_exit() const { return m_inst ? m_inst->need_exit() : false; -} \ No newline at end of file +} + +bool asst::InstHelper::sleep(unsigned millisecond) const +{ + if (need_exit()) { + return false; + } + if (millisecond == 0) { + std::this_thread::yield(); + return true; + } + auto start = std::chrono::steady_clock::now(); + Log.trace("ready to sleep", millisecond); + auto millisecond_ms = std::chrono::milliseconds(millisecond); + auto interval = millisecond_ms / 5; + + while (!need_exit()) { + std::this_thread::sleep_for(interval); + if (std::chrono::steady_clock::now() - start > millisecond_ms) { + break; + } + } + Log.trace("end of sleep", millisecond); + + return !need_exit(); +} diff --git a/src/MaaCore/InstHelper.h b/src/MaaCore/InstHelper.h index b816e967a1..2428dc7be4 100644 --- a/src/MaaCore/InstHelper.h +++ b/src/MaaCore/InstHelper.h @@ -20,6 +20,7 @@ namespace asst std::shared_ptr ctrler() const; std::shared_ptr status() const; bool need_exit() const; + bool sleep(unsigned millisecond) const; InstHelper& operator=(const InstHelper&) = default; InstHelper& operator=(InstHelper&&) noexcept = default; diff --git a/src/MaaCore/MaaCore.vcxproj b/src/MaaCore/MaaCore.vcxproj index 834f1bfd56..2399a9c25b 100644 --- a/src/MaaCore/MaaCore.vcxproj +++ b/src/MaaCore/MaaCore.vcxproj @@ -30,6 +30,7 @@ + @@ -146,6 +147,7 @@ + diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters index bbad77faaf..855761ced7 100644 --- a/src/MaaCore/MaaCore.vcxproj.filters +++ b/src/MaaCore/MaaCore.vcxproj.filters @@ -456,6 +456,9 @@ 源文件\Vision\Miscellaneous + + 源文件\Task + @@ -743,5 +746,8 @@ 源文件\Vision\Miscellaneous + + 源文件\Task + \ No newline at end of file diff --git a/src/MaaCore/Task/AbstractTask.cpp b/src/MaaCore/Task/AbstractTask.cpp index 037eaa8913..6c149c9cad 100644 --- a/src/MaaCore/Task/AbstractTask.cpp +++ b/src/MaaCore/Task/AbstractTask.cpp @@ -116,31 +116,6 @@ json::value asst::AbstractTask::basic_info_with_what(std::string what) const return info; } -bool AbstractTask::sleep(unsigned millisecond) -{ - if (need_exit()) { - return false; - } - if (millisecond == 0) { - std::this_thread::yield(); - return true; - } - auto start = std::chrono::steady_clock::now(); - Log.trace("ready to sleep", millisecond); - auto millisecond_ms = std::chrono::milliseconds(millisecond); - auto interval = millisecond_ms / 5; - - while (!need_exit()) { - std::this_thread::sleep_for(interval); - if (std::chrono::steady_clock::now() - start > millisecond_ms) { - break; - } - } - Log.trace("end of sleep", millisecond); - - return !need_exit(); -} - void asst::AbstractTask::callback(AsstMsg msg, const json::value& detail) { for (const TaskPluginPtr& plugin : m_plugins) { diff --git a/src/MaaCore/Task/AbstractTask.h b/src/MaaCore/Task/AbstractTask.h index a341c8dc41..0ae961deea 100644 --- a/src/MaaCore/Task/AbstractTask.h +++ b/src/MaaCore/Task/AbstractTask.h @@ -22,7 +22,7 @@ namespace asst class Status; class TaskData; - class AbstractTask : protected InstHelper + class AbstractTask : virtual protected InstHelper { public: AbstractTask(const AsstCallback& callback, Assistant* inst, std::string_view task_chain); @@ -63,7 +63,6 @@ namespace asst bool save_img(const std::string& dirname = "debug/"); json::value basic_info_with_what(std::string what) const; - bool sleep(unsigned millisecond); bool m_enable = true; bool m_ignore_error = false; diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp new file mode 100644 index 0000000000..aafa64a1f5 --- /dev/null +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -0,0 +1,454 @@ +#include "BattleHelper.h" + +#include +#include + +#include "Config/Miscellaneous/BattleDataConfig.h" +#include "Config/TaskData.h" +#include "Controller.h" +#include "Task/ProcessTask.h" +#include "Utils/ImageIO.hpp" +#include "Utils/Logger.hpp" +#include "Utils/NoWarningCV.h" +#include "Vision/MatchImageAnalyzer.h" +#include "Vision/Miscellaneous/BattleImageAnalyzer.h" +#include "Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h" +#include "Vision/OcrWithPreprocessImageAnalyzer.h" + +using namespace asst::battle; + +bool asst::BattleHelper::set_stage_name(const std::string& name) +{ + LogTraceFunction; + + if (!Tile.contains(name)) { + return false; + } + m_stage_name = name; + return true; +} + +bool asst::BattleHelper::calc_tiles_info(const std::string& stage_name) +{ + LogTraceFunction; + + auto normal_temp = Tile.calc(stage_name, false); + if (normal_temp.empty()) { + return false; + } + + auto side_temp = Tile.calc(stage_name, true); + if (side_temp.empty()) { + return false; + } + + m_normal_tile_info = std::move(normal_temp); + m_side_tile_info = std::move(side_temp); + + return true; +} + +bool asst::BattleHelper::pause() +{ + LogTraceFunction; + + return ProcessTask(*dynamic_cast(this), { "BattlePause" }).run(); +} + +bool asst::BattleHelper::speed_up() +{ + LogTraceFunction; + + return ProcessTask(*dynamic_cast(this), { "BattleSpeedUp" }).run(); +} + +bool asst::BattleHelper::analyze_deployment_opers(bool init) +{ + LogTraceFunction; + + if (init) { + wait_for_start(); + } + + cv::Mat image = ctrler()->get_image(); + + if (init) { + auto draw_future = std::async(std::launch::async, [&]() { save_map(image); }); + + // 识别一帧总击杀数 + BattleImageAnalyzer kills_analyzer(image); + kills_analyzer.set_target(BattleImageAnalyzer::Target::Kills); + if (kills_analyzer.analyze()) { + m_kills = kills_analyzer.get_kills(); + m_total_kills = kills_analyzer.get_total_kills(); + } + } + + BattleImageAnalyzer oper_analyzer(image); + oper_analyzer.set_target(BattleImageAnalyzer::Target::Oper); + if (!oper_analyzer.analyze()) { + return false; + } + + MatchImageAnalyzer avatar_analyzer(image); + avatar_analyzer.set_task_info("BattleAvatarData"); + + auto cur_opers = oper_analyzer.get_opers(); + std::vector unknown_opers; + + for (auto& oper : cur_opers) { + if (oper.cooling) { + continue; + } + // 把当前ROI放大一点,不然有时候像素歪了容易匹配不上 + avatar_analyzer.set_roi(oper.rect.center_zoom(1.1, WindowWidthDefault, WindowHeightDefault)); + + double max_socre = 0; + + for (const auto& [name, avatars] : m_all_deployment_avatars) { + avatar_analyzer.set_templ(avatars); + if (!avatar_analyzer.analyze()) { + continue; + } + const auto& cur_matched = avatar_analyzer.get_result(); + if (max_socre < cur_matched.score) { + max_socre = cur_matched.score; + oper.name = name; + } + } + + if (max_socre) { + m_cur_deployment_opers.insert_or_assign(oper.name, oper); + } + else { + unknown_opers.emplace_back(oper); + } + } + + if (!unknown_opers.empty()) { + // 一个都没匹配上的,挨个点开来看一下 + LogTraceScope("rec unknown opers"); + + // 暂停游戏准备识别干员 + do { + pause(); + // 在刚进入游戏的时候(画面刚刚完全亮起来的时候),点暂停是没反应的 + // 所以这里一直点,直到真的点上了为止 + if (!init || !check_pause_button()) { + break; + } + std::this_thread::yield(); + } while (!need_exit()); + + for (auto& oper : unknown_opers) { + click_oper_on_deployment(oper.rect); + + OcrWithPreprocessImageAnalyzer name_analyzer(ctrler()->get_image()); + name_analyzer.set_task_info("BattleOperName"); + name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map); + if (name_analyzer.analyze()) { + Log.error("ocr failed"); + continue; + } + name_analyzer.sort_result_by_score(); + const std::string& name = name_analyzer.get_result().front().text; + oper.name = name; + m_cur_deployment_opers.insert_or_assign(name, oper); + m_all_deployment_avatars.insert_or_assign(name, oper); + } + pause(); + } + + return true; +} + +bool asst::BattleHelper::deploy_oper(const std::string& name, const Point& loc, DeployDirection direction) +{ + LogTraceFunction; + + const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper"); + const auto use_oper_task_ptr = Task.get("BattleUseOper"); + + auto rect_opt = get_oper_rect_on_deployment(name); + if (!rect_opt) { + return false; + } + const Rect& oper_rect = *rect_opt; + + auto target_iter = m_side_tile_info.find(loc); + if (target_iter == m_side_tile_info.cend()) { + Log.error("No loc", loc); + return false; + } + const Point& target_point = target_iter->second.pos; + + int dist = static_cast( + Point::distance(target_point, { oper_rect.x + oper_rect.width / 2, oper_rect.y + oper_rect.height / 2 })); + + // 1000 是随便取的一个系数,把整数的 pre_delay 转成小数用的 + int duration = static_cast(dist / 1000.0 * swipe_oper_task_ptr->pre_delay); + bool deploy_with_pause = ctrler()->support_swipe_with_pause(); + ctrler()->swipe(oper_rect, Rect(target_point.x, target_point.y, 1, 1), duration, false, + swipe_oper_task_ptr->special_params.at(1), swipe_oper_task_ptr->special_params.at(2), + deploy_with_pause); + + // 拖动干员朝向 + if (direction != DeployDirection::None) { + static const std::unordered_map DirectionMap = { + { DeployDirection::Right, Point(1, 0) }, { DeployDirection::Down, Point(0, 1) }, + { DeployDirection::Left, Point(-1, 0) }, { DeployDirection::Up, Point(0, -1) }, + { DeployDirection::None, Point(0, 0) }, + }; + + // 计算往哪边拖动 + const Point& direction_target = DirectionMap.at(direction); + + // 将方向转换为实际的 swipe end 坐标点 + static const int coeff = swipe_oper_task_ptr->special_params.at(0); + Point end_point = target_point + (direction_target * coeff); + + ctrler()->swipe(target_point, end_point, swipe_oper_task_ptr->post_delay); + sleep(use_oper_task_ptr->post_delay); + } + + if (deploy_with_pause) { + ctrler()->press_esc(); + } + + BattlefieldOper bf_oper { .name = name, .loc = loc }; + m_battlefield_opers.emplace(name, bf_oper); + m_used_tiles.emplace(loc, bf_oper); + + return true; +} + +bool asst::BattleHelper::retreat_oper(const std::string& name) +{ + LogTraceFunction; + + auto oper_iter = m_battlefield_opers.find(name); + if (oper_iter == m_battlefield_opers.cend()) { + Log.error("No oper", name); + return false; + } + + if (!retreat_oper(oper_iter->second.loc, false)) { + return false; + } + + m_battlefield_opers.erase(name); + return true; +} + +bool asst::BattleHelper::retreat_oper(const Point& loc, bool manually) +{ + LogTraceFunction; + + if (!click_oper_on_battlefiled(loc) || !click_retreat()) { + return false; + } + + m_used_tiles.erase(loc); + if (manually) { + std::erase_if(m_battlefield_opers, [&loc](const auto& pair) -> bool { return pair.second.loc == loc; }); + } + return true; +} + +bool asst::BattleHelper::use_skill(const std::string& name) +{ + LogTraceFunction; + + auto oper_iter = m_battlefield_opers.find(name); + if (oper_iter == m_battlefield_opers.cend()) { + Log.error("No oper", name); + return false; + } + + return use_skill(oper_iter->second.loc); +} + +bool asst::BattleHelper::use_skill(const Point& loc) +{ + LogTraceFunction; + + return click_oper_on_battlefiled(loc) && click_skill(); +} + +bool asst::BattleHelper::check_pause_button() +{ + MatchImageAnalyzer battle_flag_analyzer; + battle_flag_analyzer.set_task_info("BattleOfficiallyBegin"); + battle_flag_analyzer.set_image(ctrler()->get_image()); + return battle_flag_analyzer.analyze(); +} + +bool asst::BattleHelper::wait_for_start() +{ + LogTraceFunction; + + while (!need_exit() && !check_pause_button()) { + std::this_thread::yield(); + } + return true; +} + +bool asst::BattleHelper::wait_for_end() +{ + LogTraceFunction; + + while (!need_exit() && check_pause_button()) { + use_all_ready_skill(); + std::this_thread::yield(); + } + return true; +} + +bool asst::BattleHelper::use_all_ready_skill() +{ + for (const auto& [name, loc] : m_battlefield_opers | std::views::values) { + auto& usage = m_skill_usage[name]; + if (usage != SkillUsage::Possibly && usage != SkillUsage::Once) { + continue; + } + if (!check_and_use_skill(loc)) { + continue; + } + if (usage == SkillUsage::Once) { + usage = SkillUsage::OnceUsed; + } + } +} + +bool asst::BattleHelper::check_and_use_skill(const std::string& name) +{ + LogTraceFunction; + + auto oper_iter = m_battlefield_opers.find(name); + if (oper_iter == m_battlefield_opers.cend()) { + Log.error("No oper", name); + return false; + } + return check_and_use_skill(oper_iter->second.loc); +} + +bool asst::BattleHelper::check_and_use_skill(const Point& loc) +{ + LogTraceFunction; + + BattleSkillReadyImageAnalyzer skill_analyzer(ctrler()->get_image()); + + auto target_iter = m_normal_tile_info.find(loc); + if (target_iter == m_normal_tile_info.end()) { + Log.error("No loc", loc); + return false; + } + const Point& battlefield_point = target_iter->second.pos; + skill_analyzer.set_base_point(battlefield_point); + if (!skill_analyzer.analyze()) { + return false; + } + + return use_skill(loc); +} + +void asst::BattleHelper::save_map(const cv::Mat& image) +{ + LogTraceFunction; + + using namespace asst::utils::path_literals; + const auto& MapDir = "map"_p; + + std::filesystem::create_directories(MapDir); + auto draw = image.clone(); + + for (const auto& [loc, info] : m_normal_tile_info) { + std::string text = "( " + std::to_string(loc.x) + ", " + std::to_string(loc.y) + " )"; + cv::putText(draw, text, cv::Point(info.pos.x - 30, info.pos.y), 1, 1.2, cv::Scalar(0, 0, 255), 2); + } + asst::imwrite(MapDir / asst::utils::path(m_stage_name + ".png"), draw); +} + +bool asst::BattleHelper::click_oper_on_deployment(const std::string& name) +{ + LogTraceFunction; + + auto rect_opt = get_oper_rect_on_deployment(name); + if (!rect_opt) { + return false; + } + + return click_oper_on_deployment(*rect_opt); +} + +bool asst::BattleHelper::click_oper_on_deployment(const Rect& rect) +{ + LogTraceFunction; + + const auto use_oper_task_ptr = Task.get("BattleUseOper"); + ctrler()->click(rect); + sleep(use_oper_task_ptr->pre_delay); + + return true; +} + +bool asst::BattleHelper::click_oper_on_battlefiled(const std::string& name) +{ + LogTraceFunction; + + auto oper_iter = m_battlefield_opers.find(name); + if (oper_iter == m_battlefield_opers.cend()) { + Log.error("No oper", name); + return false; + } + return click_oper_on_battlefiled(oper_iter->second.loc); +} + +bool asst::BattleHelper::click_oper_on_battlefiled(const Point& loc) +{ + LogTraceFunction; + + const auto use_oper_task_ptr = Task.get("BattleUseOper"); + + auto target_iter = m_normal_tile_info.find(loc); + if (target_iter == m_normal_tile_info.end()) { + Log.error("No loc", loc); + return false; + } + const Point& target_point = target_iter->second.pos; + + ctrler()->click(target_point); + sleep(use_oper_task_ptr->pre_delay); + + return true; +} + +bool asst::BattleHelper::click_retreat() +{ + LogTraceFunction; + + return ProcessTask(*dynamic_cast(this), { "BattleOperRetreatJustClick" }).run(); +} + +bool asst::BattleHelper::click_skill() +{ + LogTraceFunction; + + return ProcessTask(*dynamic_cast(this), { "BattleSkillReadyOnClick", "BattleSkillStopOnClick" }) + .set_task_delay(0) + .set_retry_times(1000) + .run(); +} + +std::optional asst::BattleHelper::get_oper_rect_on_deployment(const std::string& name) const +{ + LogTraceFunction; + + auto oper_iter = m_cur_deployment_opers.find(name); + if (oper_iter == m_cur_deployment_opers.cend()) { + Log.error("No oper", name); + return std::nullopt; + } + + return oper_iter->second.rect; +} diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h new file mode 100644 index 0000000000..d2382a9cab --- /dev/null +++ b/src/MaaCore/Task/BattleHelper.h @@ -0,0 +1,66 @@ +#pragma once + +#include "AbstractTask.h" + +#include "Common/AsstBattleDef.h" +#include "Config/Miscellaneous/TilePack.h" +#include "InstHelper.h" +#include "Utils/NoWarningCVMat.h" + +#include + +namespace asst +{ + class BattleHelper : virtual protected InstHelper + { + public: + using InstHelper::InstHelper; + virtual ~BattleHelper() override = default; + + protected: + virtual bool set_stage_name(const std::string& name); + bool calc_tiles_info(const std::string& stage_name); + + bool pause(); + bool speed_up(); + + bool analyze_deployment_opers(bool init = false); + + bool deploy_oper(const std::string& name, const Point& loc, battle::DeployDirection direction); + bool retreat_oper(const std::string& name); + bool retreat_oper(const Point& loc, bool manually = true); + bool use_skill(const std::string& name); + bool use_skill(const Point& loc); + bool check_pause_button(); + bool wait_for_start(); + bool wait_for_end(); + bool use_all_ready_skill(); + bool check_and_use_skill(const std::string& name); + bool check_and_use_skill(const Point& loc); + void save_map(const cv::Mat& image); + + bool click_oper_on_deployment(const std::string& name); + bool click_oper_on_deployment(const Rect& rect); + bool click_oper_on_battlefiled(const std::string& name); + bool click_oper_on_battlefiled(const Point& loc); + bool click_retreat(); // 这个是不带识别的,直接点 + bool click_skill(); // 这个是带识别的,转好了才点 + + std::optional get_oper_rect_on_deployment(const std::string& name) const; + + std::string m_stage_name; + std::unordered_map m_side_tile_info; + std::unordered_map m_normal_tile_info; + std::unordered_map m_skill_usage; + + /* 实时更新的数据 */ + int m_kills = 0; + int m_total_kills = 0; + + std::map m_all_deployment_avatars; + std::map m_cur_deployment_opers; + + std::map m_battlefield_opers; + std::map m_used_tiles; + }; +} diff --git a/src/MaaCore/Task/Miscellaneous/AutoRecruitTask.cpp b/src/MaaCore/Task/Miscellaneous/AutoRecruitTask.cpp index 81f3c45361..f956e778ee 100644 --- a/src/MaaCore/Task/Miscellaneous/AutoRecruitTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/AutoRecruitTask.cpp @@ -19,7 +19,7 @@ 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& tags, - const std::vector& all_ops = RecruitData.get_all_opers()) + const std::vector& all_ops = RecruitData.get_all_opers()) { std::vector rcs_with_single_tag; @@ -92,7 +92,7 @@ namespace asst::recruit_calc } // no senior tag, remove 6-star operators // assuming sorted by level - auto iter = ranges::find_if(comb_iter->opers, [](const RecruitOperInfo& op) { return op.level >= 6; }); + auto iter = ranges::find_if(comb_iter->opers, [](const Recruitment& op) { return op.level >= 6; }); if (iter == comb_iter->opers.end()) { ++comb_iter; continue; @@ -394,11 +394,11 @@ asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc 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 RecruitOperInfo& op) { return op.level >= 3; }); + 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 {}, - std::mem_fn(&RecruitOperInfo::level)) / + std::mem_fn(&Recruitment::level)) / static_cast(std::distance(sec, rc.opers.end())); } } @@ -437,7 +437,7 @@ asst::AutoRecruitTask::calc_task_result_type asst::AutoRecruitTask::recruit_calc comb_json["tags"] = json::array(get_tag_names(comb.tags)); std::vector opers_json_vector; - for (const RecruitOperInfo& oper_info : ranges::reverse_view(comb.opers)) { // print reversely + for (const Recruitment& oper_info : ranges::reverse_view(comb.opers)) { // print reversely json::value oper_json; oper_json["name"] = oper_info.name; oper_json["level"] = oper_info.level; diff --git a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp index d7fbda8fc9..2af189e0ea 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.cpp @@ -40,7 +40,7 @@ bool asst::BattleFormationTask::_run() } else if (has_error) { // reset page - click_role_table(role == BattleRole::Unknown ? BattleRole::Pioneer : BattleRole::Unknown); + click_role_table(role == battle::Role::Unknown ? battle::Role::Pioneer : battle::Role::Unknown); click_role_table(role); has_error = false; } @@ -164,12 +164,12 @@ bool asst::BattleFormationTask::confirm_selection() return ProcessTask(*this, { "BattleQuickFormationConfirm" }).run(); } -bool asst::BattleFormationTask::click_role_table(BattleRole role) +bool asst::BattleFormationTask::click_role_table(battle::Role role) { - static const std::unordered_map RoleNameType = { - { BattleRole::Caster, "Caster" }, { BattleRole::Medic, "Medic" }, { BattleRole::Pioneer, "Pioneer" }, - { BattleRole::Sniper, "Sniper" }, { BattleRole::Special, "Special" }, { BattleRole::Support, "Support" }, - { BattleRole::Tank, "Tank" }, { BattleRole::Warrior, "Warrior" }, + static const std::unordered_map RoleNameType = { + { battle::Role::Caster, "Caster" }, { battle::Role::Medic, "Medic" }, { battle::Role::Pioneer, "Pioneer" }, + { battle::Role::Sniper, "Sniper" }, { battle::Role::Special, "Special" }, { battle::Role::Support, "Support" }, + { battle::Role::Tank, "Tank" }, { battle::Role::Warrior, "Warrior" }, }; auto role_iter = RoleNameType.find(role); @@ -193,7 +193,7 @@ bool asst::BattleFormationTask::parse_formation() } formation.array_emplace(name); - BattleRole role = BattleData.get_role(opers_vec.front().name); + battle::Role role = BattleData.get_role(opers_vec.front().name); m_formation[role].emplace_back(opers_vec); } diff --git a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h index 559e22c2ac..5dbcad4c4f 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleFormationTask.h @@ -13,19 +13,20 @@ namespace asst void set_support_unit_name(std::string name); protected: - using OperGroup = std::vector; + using OperGroup = std::vector; + virtual bool _run() override; bool enter_selection_page(); bool select_opers_in_cur_page(std::vector& groups); void swipe_page(); bool confirm_selection(); - bool click_role_table(BattleRole role); + bool click_role_table(battle::Role role); bool parse_formation(); bool select_random_support_unit(); std::string m_stage_name; - std::unordered_map> m_formation; + std::unordered_map> m_formation; std::string m_the_right_name; std::string m_support_unit_name; }; diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 64420167ac..a57519e116 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -20,391 +20,136 @@ #include "Utils/ImageIo.hpp" -bool asst::BattleProcessTask::set_stage_name(std::string name) -{ - if (!Tile.contains(name)) { - json::value info = basic_info_with_what("UnsupportedLevel"); - auto& details = info["details"]; - details["level"] = m_stage_name; - callback(AsstMsg::SubTaskExtraInfo, info); - return false; - } - m_stage_name = std::move(name); - return true; -} +using namespace asst::battle; +using namespace asst::battle::copilot; +asst::BattleProcessTask::BattleProcessTask(const AsstCallback& callback, Assistant* inst, std::string_view task_chain) + : AbstractTask(callback, inst, task_chain), BattleHelper(inst) +{} bool asst::BattleProcessTask::_run() { - if (!get_stage_info()) { + LogTraceFunction; + + if (!calc_tiles_info(m_stage_name)) { Log.error("get stage info failed"); return false; } - json::value info = basic_info_with_what("BattleActionDoc"); - info["details"] |= json::object { - { "title", m_copilot_data.title }, - { "title_color", m_copilot_data.title_color }, - { "details", m_copilot_data.details }, - { "details_color", m_copilot_data.details_color }, - }; - callback(AsstMsg::SubTaskExtraInfo, info); + analyze_deployment_opers(true); - while (!need_exit() && !analyze_opers_preview()) { - std::this_thread::yield(); - } - - for (size_t i = 0; i < m_copilot_data.actions.size() && !need_exit(); ++i) { + for (size_t i = 0; i < m_combat_data.actions.size() && !need_exit(); ++i) { do_action(i); } return true; } -bool asst::BattleProcessTask::get_stage_info() +bool asst::BattleProcessTask::set_stage_name(const std::string& stage_name) { LogTraceFunction; - m_normal_tile_info = Tile.calc(m_stage_name, false); - m_side_tile_info = Tile.calc(m_stage_name, true); + if (!BattleHelper::set_stage_name(stage_name)) { + json::value info = basic_info_with_what("UnsupportedLevel"); + auto& details = info["details"]; + details["level"] = m_stage_name; + callback(AsstMsg::SubTaskExtraInfo, info); - if (m_side_tile_info.empty() || m_normal_tile_info.empty()) { return false; } - - m_copilot_data = Copilot.get_data(); - return true; -} - -bool asst::BattleProcessTask::analyze_opers_preview() -{ - json::value info = basic_info_with_what("BattleAction"); - info["details"] |= json::object { - { "action", "识别干员" }, - { "doc", "" }, - { "doc_color", "" }, - }; - callback(AsstMsg::SubTaskExtraInfo, info); - - MatchImageAnalyzer officially_begin_analyzer; - officially_begin_analyzer.set_task_info("BattleOfficiallyBegin"); - cv::Mat image; - while (!need_exit()) { - image = ctrler()->get_image(); - officially_begin_analyzer.set_image(image); - if (officially_begin_analyzer.analyze()) { - break; - } - std::this_thread::yield(); - } - - BattleImageAnalyzer oper_analyzer; - oper_analyzer.set_target(BattleImageAnalyzer::Target::Oper); - while (!need_exit()) { - image = ctrler()->get_image(); - oper_analyzer.set_image(image); - if (oper_analyzer.analyze()) { - break; - } - std::this_thread::yield(); - } - - // 识别一帧总击杀数 - BattleImageAnalyzer kills_analyzer(image); - kills_analyzer.set_target(BattleImageAnalyzer::Target::Kills); - if (kills_analyzer.analyze()) { - m_kills = kills_analyzer.get_kills(); - m_total_kills = kills_analyzer.get_total_kills(); - } - auto draw = image.clone(); - - // 暂停游戏准备识别干员 - // 在刚进入游戏的时候(画面刚刚完全亮起来的时候),点暂停是没反应的 - // 所以这里一直点,直到真的点上了为止 - while (!need_exit()) { - battle_pause(); - image = ctrler()->get_image(); - officially_begin_analyzer.set_image(image); - if (!officially_begin_analyzer.analyze()) { - break; - } - std::this_thread::yield(); - } - - auto draw_future = std::async(std::launch::async, [&]() { - std::filesystem::create_directory("map"); - for (const auto& [loc, info] : m_normal_tile_info) { - std::string text = "( " + std::to_string(loc.x) + ", " + std::to_string(loc.y) + " )"; - cv::putText(draw, text, cv::Point(info.pos.x - 30, info.pos.y), 1, 1.2, cv::Scalar(0, 0, 255), 2); - } - asst::imwrite("map/" + m_stage_name + ".png", draw); - }); - - auto opers = oper_analyzer.get_opers(); - - Rect cur_rect; - int click_delay = Task.get("BattleUseOper")->pre_delay; - for (size_t i = 0; i != opers.size(); ++i) { - Log.trace(__FUNCTION__, "ready to click No.", i, "oper"); - const auto& cur_oper = oper_analyzer.get_opers(); - size_t offset = opers.size() > cur_oper.size() ? opers.size() - cur_oper.size() : 0; - cur_rect = cur_oper.at(i - offset).rect; - ctrler()->click(cur_rect); - sleep(click_delay); - - image = ctrler()->get_image(); - - OcrWithPreprocessImageAnalyzer name_analyzer(image); - name_analyzer.set_task_info("BattleOperName"); - name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map); - - std::string oper_name = "Unknown"; - if (name_analyzer.analyze()) { - name_analyzer.sort_result_by_score(); - oper_name = name_analyzer.get_result().front().text; - } - opers.at(i).name = oper_name; - Log.info(__FUNCTION__, "oper's name", oper_name); - - bool not_found = true; - // 找出这个干员是哪个组里的,以及他的技能用法等 - for (const auto& [group_name, deploy_opers] : m_copilot_data.groups) { - auto iter = ranges::find_if( - deploy_opers, [&](const BattleDeployOper& deploy) -> bool { return deploy.name == oper_name; }); - if (iter != deploy_opers.cend()) { - m_group_to_oper_mapping.emplace(group_name, *iter); - not_found = false; - break; - } - } - // 没找到,可能是召唤物等新出现的 - if (not_found) { - m_group_to_oper_mapping.emplace(oper_name, BattleDeployOper { oper_name }); - } - - m_cur_opers_info.emplace(std::move(oper_name), std::move(opers.at(i))); - - // 干员特别多的时候,任意干员被点开,都会导致下方的干员图标被裁剪和移动。所以这里需要重新识别一下 - Log.trace(__FUNCTION__, "ready to analyze oper again"); - oper_analyzer.set_image(image); - oper_analyzer.analyze(); - } - - draw_future.wait(); - - ctrler()->click(cur_rect); - sleep(click_delay); - battle_pause(); - - return true; -} - -bool asst::BattleProcessTask::update_opers_info(const cv::Mat& image) -{ - BattleImageAnalyzer analyzer(image); - analyzer.set_target(BattleImageAnalyzer::Target::Oper); - if (!analyzer.analyze()) { - return false; - } - const auto& cur_opers_info = analyzer.get_opers(); - // 除非主动使用,不然可用干员数任何情况下都不会减少 - // 主动使用会 erase, 所以少了就是识别错了 - if (cur_opers_info.size() < m_cur_opers_info.size()) { - Log.error(__FUNCTION__, "Decrease in staff, Just return"); - return false; - } - - decltype(m_cur_opers_info) pre_opers_info; - m_cur_opers_info.swap(pre_opers_info); - - const int size_change = static_cast(cur_opers_info.size()) - static_cast(pre_opers_info.size()); - for (const auto& cur_oper : cur_opers_info) { - if (cur_oper.cooling) { - continue; - } - // 该干员在上一帧中可能的位置。需要考虑召唤者退场&可用召唤物消失的情况,所以 lhs-1 rhs+1 - int left_index = std::max(0, static_cast(cur_oper.index) - size_change - 1); - int right_index = static_cast(cur_oper.index + 1); - - std::vector ranged_iters; - // 找出该干员可能对应的之前的谁 - for (auto iter = pre_opers_info.cbegin(); iter != pre_opers_info.cend(); ++iter) { - int pre_index = static_cast(iter->second.index); - if (left_index <= pre_index && pre_index <= right_index) { - ranged_iters.emplace_back(iter); - } - } - - // 干员也可能是撤退下来的,把所有已使用的都拿出来比较下 - for (auto iter = m_all_opers_info.cbegin(); iter != m_all_opers_info.cend(); ++iter) { - const std::string& key = iter->first; - if (!pre_opers_info.contains(key)) { - ranged_iters.emplace_back(iter); - } - } - - std::string oper_name = "Unknown"; - MatchRect matched_result; - decltype(ranged_iters)::value_type matched_iter; - - MatchImageAnalyzer avatar_analyzer(cur_oper.avatar); - avatar_analyzer.set_task_info("BattleAvatarData"); - // 遍历比较,得分最高的那个就说明是对应的那个 - for (const auto& iter : ranged_iters) { - avatar_analyzer.set_templ(iter->second.avatar); - if (!avatar_analyzer.analyze()) { - continue; - } - if (matched_result.score < avatar_analyzer.get_result().score) { - matched_result = avatar_analyzer.get_result(); - matched_iter = iter; - } - } - // 一个都没匹配上,考虑是新增的召唤物,或者别的东西,点开来看一下 - if (matched_result.score == 0) { - battle_pause(); - ctrler()->click(cur_oper.rect); - sleep(Task.get("BattleUseOper")->pre_delay); - - OcrWithPreprocessImageAnalyzer name_analyzer(ctrler()->get_image()); - name_analyzer.set_task_info("BattleOperName"); - name_analyzer.set_replace(Task.get("CharsNameOcrReplace")->replace_map); - - if (name_analyzer.analyze()) { - name_analyzer.sort_result_by_score(); - oper_name = name_analyzer.get_result().front().text; - } - m_group_to_oper_mapping[oper_name] = BattleDeployOper { oper_name }; - ctrler()->click(cur_oper.rect); - sleep(Task.get("BattleUseOper")->pre_delay); - battle_pause(); - } - else { - oper_name = matched_iter->first; - m_used_opers.erase(oper_name); - } - - auto temp_oper = cur_oper; - temp_oper.name = oper_name; - // 保存当前干员信息 - m_all_opers_info[oper_name] = temp_oper; - m_cur_opers_info.emplace(oper_name, std::move(temp_oper)); - } return true; } bool asst::BattleProcessTask::do_action(size_t action_index) { - const auto& action = m_copilot_data.actions.at(action_index); - std::string action_desc; - switch (action.type) { - case BattleActionType::Deploy: - action_desc = "部署 " + action.group_name; - break; - case BattleActionType::Retreat: - action_desc = "撤退 " + action.group_name; - break; - case BattleActionType::UseSkill: - action_desc = "技能 " + action.group_name; - break; - case BattleActionType::SwitchSpeed: - action_desc = "切换二倍速"; - break; - case BattleActionType::SkillDaemon: - action_desc = "摆完挂机"; - break; - case BattleActionType::BulletTime: - action_desc = "子弹时间"; - break; - // TODO 其他情况 - case BattleActionType::SkillUsage: - case BattleActionType::UseAllSkill: - case BattleActionType::Output:; - } - json::value info = basic_info_with_what("BattleAction"); - info["details"] |= json::object { - { "action", action_desc }, - { "doc", action.doc }, - { "doc_color", action.doc_color }, - }; - callback(AsstMsg::SubTaskExtraInfo, info); + const auto& action = m_combat_data.actions.at(action_index); + + notify_action(action); if (!wait_condition(action)) { return false; } if (action.pre_delay > 0) { - sleep_with_possible_skill(action.pre_delay); - // 等待之后画面可能会变化,再调用一次等待条件更新干员信息 - wait_condition(action); + sleep_with_use_ready_skill(action.pre_delay); + // 等待之后画面可能会变化,更新下干员信息 + analyze_deployment_opers(); } bool ret = false; + const std::string& name = m_oper_in_group[action.group_name]; + const auto& location = action.location; + switch (action.type) { - case BattleActionType::Deploy: - ret = oper_deploy(action); + case ActionType::Deploy: + ret = deploy_oper(name, location, action.direction); + if (ret) m_in_bullet_time = false; break; - case BattleActionType::Retreat: - ret = oper_retreat(action); + + case ActionType::Retreat: + ret = m_in_bullet_time ? click_retreat() : (location.empty() ? retreat_oper(location) : retreat_oper(name)); + if (ret) m_in_bullet_time = false; break; - case BattleActionType::UseSkill: - ret = use_skill(action); + + case ActionType::UseSkill: + ret = m_in_bullet_time ? click_skill() : (location.empty() ? use_skill(location) : use_skill(name)); + if (ret) m_in_bullet_time = false; break; - case BattleActionType::SwitchSpeed: - ret = battle_speedup(); + + case ActionType::SwitchSpeed: + ret = speed_up(); break; - case BattleActionType::BulletTime: { - if (action_index + 1 > m_copilot_data.actions.size()) { - Log.error("Bullte time does not have the next step!"); - return false; - } - const auto& next_action = m_copilot_data.actions.at(action_index + 1); - if (next_action.type == BattleActionType::Deploy) { - ret = oper_deploy(next_action, true); - } - else if (next_action.type == BattleActionType::UseSkill) { - ret = use_skill(next_action, true); - } - else if (next_action.type == BattleActionType::Retreat) { - ret = oper_retreat(next_action, true); - } - else { - m_in_bullet_time = false; - Log.error("Bullte time 's next step is not deploy, skill or retreat!"); - return false; - } - m_in_bullet_time = true; - } break; - case BattleActionType::SkillUsage: { - auto& oper_info = m_group_to_oper_mapping[action.group_name]; - oper_info.skill_usage = action.modify_usage; - m_used_opers[oper_info.name].info.skill_usage = action.modify_usage; + + case ActionType::BulletTime: + ret = enter_bullet_time_for_next_action(action_index + 1, location, name); + if (ret) m_in_bullet_time = true; + break; + + case ActionType::SkillUsage: + m_skill_usage[action.group_name] = action.modify_usage; ret = true; - } break; - case BattleActionType::Output: + break; + + case ActionType::Output: // DoNothing + ret = true; break; - case BattleActionType::SkillDaemon: - ret = wait_to_end(action); + + case ActionType::SkillDaemon: + ret = wait_for_end(); break; } - if (action.type != BattleActionType::BulletTime) { - m_in_bullet_time = false; - } - sleep_with_possible_skill(action.post_delay); + sleep_with_use_ready_skill(action.post_delay); return ret; } -bool asst::BattleProcessTask::wait_condition(const BattleAction& action) +bool asst::BattleProcessTask::notify_action(const battle::copilot::Action& action) { - cv::Mat image = ctrler()->get_image(); + const static std::unordered_map ActionNames = { + { ActionType::Deploy, "Deploy" }, { ActionType::UseSkill, "UseSkill" }, + { ActionType::Retreat, "Retreat" }, { ActionType::SkillDaemon, "SkillDaemon" }, + { ActionType::SwitchSpeed, "SwitchSpeed" }, { ActionType::SkillUsage, "SkillUsage" }, + { ActionType::BulletTime, "BulletTime" }, { ActionType::Output, "Output" }, + }; + json::value info = basic_info_with_what("Action"); + info["details"] |= json::object { + { "action", ActionNames.at(action.type) }, + { "target", action.group_name }, + { "doc", action.doc }, + { "doc_color", action.doc_color }, + }; + callback(AsstMsg::SubTaskExtraInfo, info); +} + +bool asst::BattleProcessTask::wait_condition(const Action& action) +{ // 计算初始状态 int cost_base = -1; // int cooling_base = -1; if (action.cost_changes != 0) { - BattleImageAnalyzer analyzer(image); + BattleImageAnalyzer analyzer(ctrler()->get_image()); analyzer.set_target(BattleImageAnalyzer::Target::Cost); if (analyzer.analyze()) { cost_base = analyzer.get_cost(); @@ -420,11 +165,8 @@ bool asst::BattleProcessTask::wait_condition(const BattleAction& action) // } // 计算击杀数 - while (m_kills < action.kills) { - if (need_exit()) { - return false; - } - BattleImageAnalyzer analyzer(image); + while (!need_exit() && m_kills < action.kills) { + BattleImageAnalyzer analyzer(ctrler()->get_image()); if (m_total_kills) { analyzer.set_pre_total_kills(m_total_kills); } @@ -436,22 +178,18 @@ bool asst::BattleProcessTask::wait_condition(const BattleAction& action) break; } } - - try_possible_skill(image); - std::this_thread::yield(); - image = ctrler()->get_image(); + use_all_ready_skill(); } // 计算费用变化量 if (action.cost_changes != 0 || action.costs) { while (!need_exit()) { - BattleImageAnalyzer analyzer(image); + BattleImageAnalyzer analyzer(ctrler()->get_image()); analyzer.set_target(BattleImageAnalyzer::Target::Cost); if (analyzer.analyze()) { int cost = analyzer.get_cost(); if (cost_base == -1) { cost_base = cost; - image = ctrler()->get_image(); continue; } if (action.cost_changes != 0) { @@ -460,26 +198,18 @@ bool asst::BattleProcessTask::wait_condition(const BattleAction& action) break; } } - if (action.costs) { - if (cost >= action.costs) { - break; - } + if (action.costs && cost >= action.costs) { + break; } } - - try_possible_skill(image); - std::this_thread::yield(); - image = ctrler()->get_image(); + use_all_ready_skill(); } } // 计算有几个干员在cd if (action.cooling >= 0) { - while (true) { - if (need_exit()) { - return false; - } - BattleImageAnalyzer analyzer(image); + while (!need_exit()) { + BattleImageAnalyzer analyzer(ctrler()->get_image()); analyzer.set_target(BattleImageAnalyzer::Target::Oper); if (analyzer.analyze()) { int cooling_count = static_cast( @@ -488,462 +218,65 @@ bool asst::BattleProcessTask::wait_condition(const BattleAction& action) break; } } - - try_possible_skill(image); - std::this_thread::yield(); - image = ctrler()->get_image(); + use_all_ready_skill(); } } // 部署干员还有额外等待费用够或 CD 转好 - if (!m_in_bullet_time && action.type == BattleActionType::Deploy) { - const std::string& name = m_group_to_oper_mapping[action.group_name].name; - while (true) { - if (need_exit()) { - return false; - } - update_opers_info(image); - - if (auto iter = m_cur_opers_info.find(name); iter != m_cur_opers_info.cend() && iter->second.available) { + if (!m_in_bullet_time && action.type == ActionType::Deploy) { + const std::string& name = m_oper_in_group[action.group_name]; + while (!need_exit()) { + analyze_deployment_opers(); + if (auto iter = m_cur_deployment_opers.find(name); + iter != m_cur_deployment_opers.cend() && iter->second.available) { break; } - - try_possible_skill(image); - std::this_thread::yield(); - image = ctrler()->get_image(); + use_all_ready_skill(); } } return true; } -bool asst::BattleProcessTask::oper_deploy(const BattleAction& action, bool only_pre_process) +bool asst::BattleProcessTask::enter_bullet_time_for_next_action(size_t next_index, const Point& location, + const std::string& name) { - const auto& oper_info = m_group_to_oper_mapping[action.group_name]; - auto iter = m_cur_opers_info.find(oper_info.name); - if (iter == m_cur_opers_info.cend()) { - Log.error("Can't find oper info for", oper_info.name); + LogTraceFunction; + + if (next_index > m_combat_data.actions.size()) { + Log.error("Bullet time does not have the next step!"); return false; } - Rect oper_rect = iter->second.rect; + const auto& next_action = m_combat_data.actions.at(next_index); - if (only_pre_process && !m_in_bullet_time) { - // 点击干员进入子弹时间 - ctrler()->click(oper_rect); - return true; + bool ret = false; + switch (next_action.type) { + case ActionType::Deploy: + ret = click_oper_on_deployment(name); + break; + + case ActionType::UseSkill: + case ActionType::Retreat: + ret = location.empty() ? click_oper_on_battlefiled(location) : click_oper_on_battlefiled(name); + break; + + default: + Log.error("Bullet time 's next step is not deploy, skill or retreat!"); + return false; } - const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper"); - const auto use_oper_task_ptr = Task.get("BattleUseOper"); - - // 拖动到场上 - Point placed_point = m_side_tile_info[action.location].pos; - - Rect placed_rect { placed_point.x, placed_point.y, 0, 0 }; - int dist = static_cast( - Point::distance(placed_point, { oper_rect.x + oper_rect.width / 2, oper_rect.y + oper_rect.height / 2 })); - // 1000 是随便取的一个系数,把整数的 pre_delay 转成小数用的 - int duration = static_cast(dist / 1000.0 * swipe_oper_task_ptr->pre_delay); - bool deploy_with_pause = ctrler()->support_swipe_with_pause(); - ctrler()->swipe(oper_rect, placed_rect, duration, false, swipe_oper_task_ptr->special_params.at(1), - swipe_oper_task_ptr->special_params.at(2), deploy_with_pause); - - sleep(use_oper_task_ptr->post_delay); - - // 拖动干员朝向 - if (action.direction != BattleDeployDirection::None) { - static const std::unordered_map DirectionMapping = { - { BattleDeployDirection::Right, Point(1, 0) }, { BattleDeployDirection::Down, Point(0, 1) }, - { BattleDeployDirection::Left, Point(-1, 0) }, { BattleDeployDirection::Up, Point(0, -1) }, - { BattleDeployDirection::None, Point(0, 0) }, - }; - - // 计算往哪边拖动 - Point direction = DirectionMapping.at(action.direction); - - // 将方向转换为实际的 swipe end 坐标点 - static const int coeff = Task.get("BattleSwipeOper")->special_params.at(0); - Point end_point = placed_point + (direction * coeff); - - ctrler()->swipe(placed_point, end_point, swipe_oper_task_ptr->post_delay); - sleep(use_oper_task_ptr->post_delay); - } - - if (deploy_with_pause) { - ctrler()->press_esc(); - } - - m_used_opers[iter->first] = - BattleDeployInfo { action.location, m_normal_tile_info[action.location].pos, oper_info }; - - m_cur_opers_info.erase(iter); - return true; } -bool asst::BattleProcessTask::oper_retreat(const BattleAction& action, bool only_pre_process) +void asst::BattleProcessTask::sleep_with_use_ready_skill(unsigned millisecond) { - if (!m_in_bullet_time) { - const std::string& name = m_group_to_oper_mapping[action.group_name].name; - Point pos; - if (auto iter = m_used_opers.find(name); - action.location.x == 0 && action.location.y == 0 && iter != m_used_opers.cend()) { - pos = iter->second.pos; - m_used_opers.erase(iter); - } - else { - pos = m_normal_tile_info.at(action.location).pos; - } - ctrler()->click(pos); - sleep(Task.get("BattleUseOper")->pre_delay); - } - if (only_pre_process) { - return true; - } + LogTraceScope(__FUNCTION__ + std::to_string(millisecond)); - return ProcessTask(*this, { "BattleOperRetreatJustClick" }).run(); -} - -bool asst::BattleProcessTask::use_skill(const BattleAction& action, bool only_pre_process) -{ - if (!m_in_bullet_time) { - const std::string& name = m_group_to_oper_mapping[action.group_name].name; - Point pos; - if (auto iter = m_used_opers.find(name); - action.location.x == 0 && action.location.y == 0 && iter != m_used_opers.cend()) { - pos = iter->second.pos; - } - else { - pos = m_normal_tile_info.at(action.location).pos; - } - - ctrler()->click(pos); - sleep(Task.get("BattleUseOper")->pre_delay); - } - - if (only_pre_process) { - return true; - } - - return ProcessTask(*this, { "BattleSkillReadyOnClick", "BattleSkillStopOnClick" }) - .set_task_delay(0) - .set_retry_times(10000) - .run(); -} - -bool asst::BattleProcessTask::wait_to_end(const BattleAction& action) -{ - std::ignore = action; - - MatchImageAnalyzer officially_begin_analyzer; - officially_begin_analyzer.set_task_info("BattleOfficiallyBegin"); - cv::Mat image; - while (!need_exit()) { - image = ctrler()->get_image(); - officially_begin_analyzer.set_image(image); - if (!officially_begin_analyzer.analyze()) { - break; - } - try_possible_skill(image); - std::this_thread::yield(); - } - return true; -} - -bool asst::BattleProcessTask::try_possible_skill(const cv::Mat& image) -{ - BattleSkillReadyImageAnalyzer skill_analyzer(image); - bool used = false; - for (auto& info : m_used_opers | views::values) { - if (info.info.skill_usage != BattleSkillUsage::Possibly && info.info.skill_usage != BattleSkillUsage::Once) { - continue; - } - skill_analyzer.set_base_point(info.pos); - if (!skill_analyzer.analyze()) { - continue; - } - - ctrler()->click(info.pos); - sleep(Task.get("BattleUseOper")->pre_delay); - used |= ProcessTask(*this, { "BattleSkillReadyOnClick" }).set_task_delay(0).run(); - if (info.info.skill_usage == BattleSkillUsage::Once) { - info.info.skill_usage = BattleSkillUsage::OnceUsed; - } - } - return used; -} - -void asst::BattleProcessTask::sleep_with_possible_skill(unsigned millisecond) -{ - if (need_exit()) { - return; - } - if (millisecond == 0) { - return; - } + using namespace std::chrono_literals; auto start = std::chrono::steady_clock::now(); - long long duration = 0; - Log.trace("ready to sleep_with_possible_skill", millisecond); - - while (!need_exit() && duration < millisecond) { - duration = - std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); - try_possible_skill(ctrler()->get_image()); + while (!need_exit() && std::chrono::steady_clock::now() - start < millisecond * 1ms) { + use_all_ready_skill(); std::this_thread::yield(); } - Log.trace("end of sleep_with_possible_skill", millisecond); -} - -bool asst::BattleProcessTask::battle_pause() -{ - return ProcessTask(*this, { "BattlePause" }).run(); -} - -bool asst::BattleProcessTask::battle_speedup() -{ - return ProcessTask(*this, { "BattleSpeedUp" }).run(); -} - -template -std::optional> asst::BattleProcessTask:: - get_char_allocation_for_each_group(const std::unordered_map>& group_list, - const std::vector& char_list) -{ - /* - * * dlx 算法简介 - * - * https://oi-wiki.org/search/dlx/ - * - * - * * dlx 算法作用 - * - * 在形如: - * a: 10010 - * b: 01110 - * c: 01001 - * d: 00100 - * e: 11010 - * 这样的数据里, - * dlx 可以找到 {a, c, d} 这样每列恰好出现且仅出现一次 1 的数据, - * 也即对全集的一个精确覆盖: - * a: 10010 - * c: 01001 - * d: 00100 - * 11111 - * - * - * * dlx 算法建模 - * - * dlx 的列分为 [组号] [干员号] 两部分 - * dlx 的行分为 [可能的选择对] [不选择该干员] 两部分 - * - * [可能的选择对]: - * 每行对应一种可能的选择, - * 将组号,干员号对应位置的列设为1 - * - * [不选择该干员]: - * 每行对应不选择某干员的情况, - * 将干员号对应位置的列设为1 - * - * - * * dlx 建模示例 - * - * 有以下分组: - * a: {1, 3, 4} - * b: {2, 3, 5} - * c: {1, 2, 3} - * 拥有的干员: - * {1, 2, 4, 5, 6} - * - * 先处理出所有可能的情况: - * a: {1, 4} - * b: {2, 5} - * c: {1, 2} - * - * 构造表: - * abc 1245 - * 1 100 1000 - * 2 100 0010 - * 3 010 0100 - * 4 010 0001 - * 5 001 1000 - * 6 001 0100 - * 7 000 1000 ~1 - * 9 000 0100 ~2 - * 9 000 0010 ~4 - * A 000 0001 ~5 - * - * 使用dlx求得一组解: - * 一个可能的结果是: - * 行号 {2, 3, 5, A} - * 即 {, , , ~5} - * - * 输出分组结果: - * a: 4 - * b: 2 - * c: 1 - * - */ - - // dlx 算法模板类 - class DancingLinksModel - { - private: - size_t index {}; - std::vector first, size; - std::vector left, right, up, down; - std::vector column, row; - - void remove(const size_t& column_id) - { - left[right[column_id]] = left[column_id]; - right[left[column_id]] = right[column_id]; - for (size_t i = down[column_id]; i != column_id; i = down[i]) { - for (size_t j = right[i]; j != i; j = right[j]) { - up[down[j]] = up[j]; - down[up[j]] = down[j]; - --size[column[j]]; - } - } - } - - void recover(const size_t& column_id) - { - for (size_t i = up[column_id]; i != column_id; i = up[i]) { - for (size_t j = left[i]; j != i; j = left[j]) { - up[down[j]] = down[up[j]] = j; - ++size[column[j]]; - } - } - left[right[column_id]] = right[left[column_id]] = column_id; - } - - public: - size_t answer_stack_size {}; - std::vector answer_stack; - - DancingLinksModel(const size_t& max_node_num, const size_t& max_ans_size) - : first(max_node_num), size(max_node_num), left(max_node_num), right(max_node_num), up(max_node_num), - down(max_node_num), column(max_node_num), row(max_node_num), answer_stack(max_ans_size) - {} - - void build(const size_t& column_id) - { - for (size_t i = 0; i <= column_id; i++) { - left[i] = i - 1; - right[i] = i + 1; - up[i] = down[i] = i; - } - left[0] = column_id; - right[column_id] = 0; - index = column_id; - first.clear(); - size.clear(); - } - - void insert(const size_t& row_id, const size_t& column_id) - { - column[++index] = column_id; - row[index] = row_id; - ++size[column_id]; - down[index] = down[column_id]; - up[down[column_id]] = index; - up[index] = column_id; - down[column_id] = index; - if (!first[row_id]) { - first[row_id] = left[index] = right[index] = index; - } - else { - right[index] = right[first[row_id]]; - left[right[first[row_id]]] = index; - left[index] = first[row_id]; - right[first[row_id]] = index; - } - } - - bool dance(const size_t& depth) - { - if (!right[0]) { - answer_stack_size = depth; - return true; - } - size_t column_id = right[0]; - for (size_t i = right[0]; i != 0; i = right[i]) { - if (size[i] < size[column_id]) { - column_id = i; - } - } - remove(column_id); - for (size_t i = down[column_id]; i != column_id; i = down[i]) { - answer_stack[depth] = row[i]; - for (size_t j = right[i]; j != i; j = right[j]) { - remove(column[j]); - } - if (dance(depth + 1)) { - return true; - } - for (size_t j = left[i]; j != i; j = left[j]) { - recover(column[j]); - } - } - recover(column_id); - return false; - } - }; - - // 建立结点、组、干员与各自 id 的映射关系 - std::vector> node_id_mapping; - std::vector group_id_mapping; - std::vector char_id_mapping; - std::unordered_map group_name_mapping; - std::unordered_map char_name_mapping; - std::set char_set(char_list.begin(), char_list.end()); - - for (auto& i : group_list) { - group_name_mapping[i.first] = group_id_mapping.size(); - group_id_mapping.emplace_back(i.first); - for (auto& j : i.second) { - if (char_set.contains(j)) { - node_id_mapping.emplace_back(i.first, j); - if (!char_name_mapping.contains(j)) { - char_name_mapping[j] = char_id_mapping.size(); - char_id_mapping.emplace_back(j); - } - } - } - } - - // 建 01 矩阵 - const size_t node_num = node_id_mapping.size(); - const size_t group_num = group_id_mapping.size(); - const size_t char_num = char_id_mapping.size(); - - DancingLinksModel dancing_links_model(2 * node_num + group_num + 2 * char_num + 1, group_num + char_num); - - dancing_links_model.build(group_num + char_num); - - for (size_t i = 0; i < node_num; i++) { - dancing_links_model.insert(i + 1, group_name_mapping[node_id_mapping[i].first] + 1); - dancing_links_model.insert(i + 1, group_num + char_name_mapping[node_id_mapping[i].second] + 1); - } - - for (size_t i = 0; i < char_num; i++) { - dancing_links_model.insert(i + node_num + 1, i + group_num + 1); - } - - // dance!! - bool has_solution = dancing_links_model.dance(0); - - // 判定结果 - if (!has_solution) return std::nullopt; - - std::unordered_map return_value; - - for (size_t i = 0; i < dancing_links_model.answer_stack_size; i++) { - if (dancing_links_model.answer_stack[i] > node_num) break; - return_value.insert(node_id_mapping[dancing_links_model.answer_stack[i] - 1]); - } - - return return_value; } diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h index d731235979..fd41223515 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h @@ -1,5 +1,6 @@ #pragma once #include "Task/AbstractTask.h" +#include "Task/BattleHelper.h" #include "Common/AsstBattleDef.h" #include "Common/AsstTypes.h" @@ -8,52 +9,25 @@ namespace asst { - class BattleProcessTask : public AbstractTask + class BattleProcessTask : public AbstractTask, public BattleHelper { public: - using AbstractTask::AbstractTask; + BattleProcessTask(const AsstCallback& callback, Assistant* inst, std::string_view task_chain); virtual ~BattleProcessTask() override = default; - bool set_stage_name(std::string name); + virtual bool set_stage_name(const std::string& stage_name) override; protected: virtual bool _run() override; - virtual bool get_stage_info(); - bool battle_pause(); - bool battle_speedup(); - bool analyze_opers_preview(); - bool update_opers_info(const cv::Mat& image); - bool do_action(size_t action_index); - bool wait_condition(const BattleAction& action); + bool notify_action(const battle::copilot::Action& action); + bool wait_condition(const battle::copilot::Action& action); + bool enter_bullet_time_for_next_action(size_t next_index, const Point& location, const std::string& name); + void sleep_with_use_ready_skill(unsigned millisecond); - bool oper_deploy(const BattleAction& action, bool only_pre_process = false); - bool oper_retreat(const BattleAction& action, bool only_pre_process = false); - bool use_skill(const BattleAction& action, bool only_pre_process = false); - bool wait_to_end(const BattleAction& action); - - bool try_possible_skill(const cv::Mat& image); - void sleep_with_possible_skill(unsigned millisecond); - - template - static std::optional> get_char_allocation_for_each_group( - const std::unordered_map>& group_list, - const std::vector& char_list); - - std::string m_stage_name; - - std::unordered_map m_side_tile_info; - std::unordered_map m_normal_tile_info; - BattleCopilotData m_copilot_data; - std::unordered_map m_group_to_oper_mapping; - - /* 实时更新的数据 */ - int m_kills = 0; - int m_total_kills = 0; - std::unordered_map m_all_opers_info; - std::unordered_map m_cur_opers_info; - std::unordered_map m_used_opers; + battle::copilot::CombatData m_combat_data; + std::unordered_map m_oper_in_group; bool m_in_bullet_time = false; }; diff --git a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp index 0ea548a585..dc6ea602dc 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.cpp @@ -20,6 +20,14 @@ #include "Vision/Miscellaneous/BattleSkillReadyImageAnalyzer.h" #include "Vision/OcrWithPreprocessImageAnalyzer.h" +using namespace asst::battle; +using namespace asst::battle::roguelike; + +asst::RoguelikeBattleTaskPlugin::RoguelikeBattleTaskPlugin(const AsstCallback& callback, Assistant* inst, + std::string_view task_chain) + : AbstractTaskPlugin(callback, inst, task_chain), BattleHelper(inst) +{} + bool asst::RoguelikeBattleTaskPlugin::verify(AsstMsg msg, const json::value& details) const { if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") { @@ -196,13 +204,14 @@ bool asst::RoguelikeBattleTaskPlugin::get_stage_info() else { for (const auto& [loc, side] : m_normal_tile_info) { if (side.key == TilePack::TileKey::Home) { - m_homes.emplace_back(ReplacementHome { loc, BattleDeployDirection::None }); + m_homes.emplace_back(ReplacementHome { loc, battle::DeployDirection::None }); } } m_stage_use_dice = true; m_role_order = { - BattleRole::Warrior, BattleRole::Pioneer, BattleRole::Medic, BattleRole::Tank, BattleRole::Sniper, - BattleRole::Caster, BattleRole::Support, BattleRole::Special, BattleRole::Drone, + battle::Role::Warrior, battle::Role::Pioneer, battle::Role::Medic, + battle::Role::Tank, battle::Role::Sniper, battle::Role::Caster, + battle::Role::Support, battle::Role::Special, battle::Role::Drone, }; } m_wait_blocking.assign(m_homes.size(), true); @@ -243,70 +252,70 @@ bool asst::RoguelikeBattleTaskPlugin::battle_pause() return ProcessTask(*this, { "BattlePause" }).run(); } -asst::BattleLocationType asst::RoguelikeBattleTaskPlugin::get_role_location_type(const BattleRole& role) +asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_role_location_type(const battle::Role& role) { switch (role) { - case BattleRole::Medic: - case BattleRole::Support: - case BattleRole::Sniper: - case BattleRole::Caster: - return BattleLocationType::Ranged; + case battle::Role::Medic: + case battle::Role::Support: + case battle::Role::Sniper: + case battle::Role::Caster: + return battle::LocationType::Ranged; break; - case BattleRole::Pioneer: - case BattleRole::Warrior: - case BattleRole::Tank: - case BattleRole::Special: - case BattleRole::Drone: - return BattleLocationType::Melee; + case battle::Role::Pioneer: + case battle::Role::Warrior: + case battle::Role::Tank: + case battle::Role::Special: + case battle::Role::Drone: + return battle::LocationType::Melee; break; default: - return BattleLocationType::None; + return battle::LocationType::None; break; } } -asst::BattleLocationType asst::RoguelikeBattleTaskPlugin::get_oper_location_type(const std::string& name) +asst::battle::LocationType asst::RoguelikeBattleTaskPlugin::get_oper_location_type(const std::string& name) { return BattleData.get_location_type(name); } -asst::BattleOperPosition asst::RoguelikeBattleTaskPlugin::get_role_position(const BattleRole& role) +asst::battle::OperPosition asst::RoguelikeBattleTaskPlugin::get_role_position(const battle::Role& role) { switch (role) { - case BattleRole::Support: - case BattleRole::Sniper: - case BattleRole::Caster: - return BattleOperPosition::AirDefense; + case battle::Role::Support: + case battle::Role::Sniper: + case battle::Role::Caster: + return battle::OperPosition::AirDefense; break; - case BattleRole::Pioneer: - case BattleRole::Warrior: - case BattleRole::Tank: - return BattleOperPosition::Blocking; + case battle::Role::Pioneer: + case battle::Role::Warrior: + case battle::Role::Tank: + return battle::OperPosition::Blocking; break; - case BattleRole::Medic: - case BattleRole::Special: - case BattleRole::Drone: + case battle::Role::Medic: + case battle::Role::Special: + case battle::Role::Drone: default: - return BattleOperPosition::None; + return battle::OperPosition::None; break; } } -void asst::RoguelikeBattleTaskPlugin::set_position_full(const BattleLocationType& loc_type, bool full) +void asst::RoguelikeBattleTaskPlugin::set_position_full(const battle::LocationType& loc_type, bool full) { switch (loc_type) { - case BattleLocationType::Melee: + case battle::LocationType::Melee: m_melee_full = full; break; - case BattleLocationType::Ranged: + case battle::LocationType::Ranged: m_ranged_full = full; break; - case BattleLocationType::All: + case battle::LocationType::All: m_melee_full = full; m_ranged_full = full; break; - case BattleLocationType::Invalid: - case BattleLocationType::None: + case battle::LocationType::Invalid: + case battle::LocationType::None: default: break; } @@ -319,7 +328,7 @@ void asst::RoguelikeBattleTaskPlugin::set_position_full(const Point& point, bool } } -void asst::RoguelikeBattleTaskPlugin::set_position_full(const BattleRole& role, bool full) +void asst::RoguelikeBattleTaskPlugin::set_position_full(const battle::Role& role, bool full) { set_position_full(get_role_location_type(role), full); } @@ -329,21 +338,21 @@ void asst::RoguelikeBattleTaskPlugin::set_position_full(const std::string& name, set_position_full(get_oper_location_type(name), full); } -bool asst::RoguelikeBattleTaskPlugin::get_position_full(const BattleRole& role) +bool asst::RoguelikeBattleTaskPlugin::get_position_full(const battle::Role& role) { const auto& loc_type = get_role_location_type(role); switch (loc_type) { - case BattleLocationType::Melee: + case battle::LocationType::Melee: return m_melee_full; break; - case BattleLocationType::Ranged: + case battle::LocationType::Ranged: return m_ranged_full; break; - case BattleLocationType::All: + case battle::LocationType::All: return m_melee_full && m_ranged_full; break; - case BattleLocationType::Invalid: - case BattleLocationType::None: + case battle::LocationType::Invalid: + case battle::LocationType::None: default: break; } @@ -396,13 +405,13 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() std::vector cooling_opers; const auto use_oper_task_ptr = Task.get("BattleUseOper"); bool has_dice = false; - BattleRealTimeOper dice; + battle::DeploymentOper dice; for (const auto& oper : opers) { if (oper.cooling) cooling_count++; if (oper.available) available_count++; } for (auto& oper : opers) { - if (oper.role != BattleRole::Drone) { + if (oper.role != battle::Role::Drone) { continue; } if (m_dice_image.empty()) { @@ -437,7 +446,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() } size_t offset = opers.size() > cur_opers.size() ? opers.size() - cur_opers.size() : 0; const auto& oper = cur_opers.at(i - offset); - if ((!oper.cooling) || oper.role == BattleRole::Drone) { + if ((!oper.cooling) || oper.role == battle::Role::Drone) { continue; } ctrler()->click(oper.rect); @@ -485,7 +494,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() m_medic_for_home_index.erase(del_pos_medic); } if (auto del_pos_tiles = m_used_tiles.find(iter->second); del_pos_tiles != m_used_tiles.end()) { - if (m_normal_tile_info[del_pos_tiles->first].buildable == BattleLocationType::Melee) { + if (m_normal_tile_info[del_pos_tiles->first].buildable == battle::LocationType::Melee) { m_force_air_defense.has_deployed_blocking_num--; } m_used_tiles.erase(del_pos_tiles); @@ -532,7 +541,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() const auto swipe_oper_task_ptr = Task.get("BattleSwipeOper"); // 点击当前最合适的干员 - BattleRealTimeOper opt_oper; + battle::DeploymentOper opt_oper; bool oper_found = false; bool has_blocking = false; @@ -546,7 +555,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() if (m_use_dice) { opt_oper = std::move(dice); oper_found = true; - if (available_locations(BattleLocationType::Melee).empty()) { + if (available_locations(battle::LocationType::Melee).empty()) { m_melee_full = true; Log.info("Tiles full"); return true; @@ -560,15 +569,15 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() if (op.cooling) { continue; } - if (op.role == BattleRole::Medic) { + if (op.role == battle::Role::Medic) { has_medic = true; } - BattleOperPosition position = get_role_position(op.role); - if (position == BattleOperPosition::Blocking) { + battle::OperPosition position = get_role_position(op.role); + if (position == battle::OperPosition::Blocking) { has_blocking = true; } - else if (position == BattleOperPosition::AirDefense) { - if (m_force_air_defense.ban_medic && op.role == BattleRole::Medic) { + else if (position == battle::OperPosition::AirDefense) { + if (m_force_air_defense.ban_medic && op.role == battle::Role::Medic) { continue; } has_air_defense = true; @@ -586,17 +595,17 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() } } for (auto role : m_role_order) { - BattleOperPosition position = get_role_position(role); + battle::OperPosition position = get_role_position(role); if (force_need_air_defense) { - if (position != BattleOperPosition::AirDefense) continue; - if (m_force_air_defense.ban_medic && role == BattleRole::Medic) continue; + if (position != battle::OperPosition::AirDefense) continue; + if (m_force_air_defense.ban_medic && role == battle::Role::Medic) continue; } else { if (wait_blocking) { - if (position != BattleOperPosition::Blocking) continue; + if (position != battle::OperPosition::Blocking) continue; } else if (wait_medic) { - if (role != BattleRole::Medic) { + if (role != battle::Role::Medic) { continue; } } @@ -650,7 +659,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() } size_t offset = opers.size() > cur_opers.size() ? opers.size() - cur_opers.size() : 0; const auto& oper = cur_opers.at(i - offset); - if (oper.role == BattleRole::Drone) { + if (oper.role == battle::Role::Drone) { clicked_drone = true; ctrler()->click(oper.rect); sleep(use_oper_task_ptr->pre_delay); @@ -691,14 +700,14 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() oper_name_analyzer.sort_result_by_score(); opt_oper.name = oper_name_analyzer.get_result().front().text; } - if (opt_oper.name == "阿米娅" && opt_oper.role == BattleRole::Warrior) { + if (opt_oper.name == "阿米娅" && opt_oper.role == battle::Role::Warrior) { opt_oper.name = "阿米娅-WARRIOR"; } if (!is_oper_name_error(opt_oper.name)) { auto real_loc_type = get_oper_location_type(opt_oper.name); - if (real_loc_type != BattleLocationType::Invalid && // 说明名字识别错了 - real_loc_type != BattleLocationType::All && real_loc_type != get_role_location_type(opt_oper.role)) { + if (real_loc_type != battle::LocationType::Invalid && // 说明名字识别错了 + real_loc_type != battle::LocationType::All && real_loc_type != get_role_location_type(opt_oper.role)) { // 重新计算干员是否有地方放 if (available_locations(opt_oper.name).empty()) { set_position_full(opt_oper.name, true); @@ -748,7 +757,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() sleep(use_oper_task_ptr->post_delay); } - if (opt_oper.role == BattleRole::Drone) { + if (opt_oper.role == battle::Role::Drone) { cancel_oper_selection(); now_time = std::chrono::system_clock::now(); if (opt_oper.name == Dice) { @@ -767,7 +776,7 @@ bool asst::RoguelikeBattleTaskPlugin::auto_battle() m_used_tiles.emplace(placed_loc, opt_oper.name); m_opers_in_field.emplace(opt_oper.name, placed_loc); m_retreated_opers.erase(opt_oper.name); - if (get_role_position(opt_oper.role) == BattleOperPosition::Blocking) { + if (get_role_position(opt_oper.role) == battle::OperPosition::Blocking) { m_force_air_defense.has_deployed_blocking_num++; } if (force_need_air_defense) { @@ -858,7 +867,7 @@ void asst::RoguelikeBattleTaskPlugin::all_melee_retreat() for (const auto& loc : m_used_tiles | views::keys) { auto& tile_info = m_normal_tile_info[loc]; auto& type = tile_info.buildable; - if (type == BattleLocationType::Melee || type == BattleLocationType::All) { + if (type == battle::LocationType::Melee || type == battle::LocationType::All) { retreat(tile_info.pos); } } @@ -917,13 +926,13 @@ bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image) bool used = false; for (auto& [loc, oper_name] : m_used_tiles) { std::string status_key = Status::RoguelikeSkillUsagePrefix + oper_name; - auto usage = BattleSkillUsage::Possibly; + auto usage = battle::SkillUsage::Possibly; auto usage_opt = status()->get_number(status_key); if (usage_opt) { - usage = static_cast(usage_opt.value()); + usage = static_cast(usage_opt.value()); } - if (usage != BattleSkillUsage::Possibly && usage != BattleSkillUsage::Once) { + if (usage != battle::SkillUsage::Possibly && usage != battle::SkillUsage::Once) { continue; } const Point pos = m_normal_tile_info.at(loc).pos; @@ -938,9 +947,9 @@ bool asst::RoguelikeBattleTaskPlugin::try_possible_skill(const cv::Mat& image) cancel_oper_selection(); } used |= ret; - if (usage == BattleSkillUsage::Once) { - status()->set_number(status_key, static_cast(BattleSkillUsage::OnceUsed)); - m_restore_status[status_key] = static_cast(BattleSkillUsage::Once); + if (usage == battle::SkillUsage::Once) { + status()->set_number(status_key, static_cast(battle::SkillUsage::OnceUsed)); + m_restore_status[status_key] = static_cast(battle::SkillUsage::Once); } } return used; @@ -1029,10 +1038,10 @@ bool asst::RoguelikeBattleTaskPlugin::cancel_oper_selection() bool asst::RoguelikeBattleTaskPlugin::is_oper_name_error(const std::string& name) { - return name == UnknownName || get_oper_location_type(name) == BattleLocationType::Invalid; + return name == UnknownName || get_oper_location_type(name) == battle::LocationType::Invalid; } -std::vector asst::RoguelikeBattleTaskPlugin::available_locations(BattleRole role) +std::vector asst::RoguelikeBattleTaskPlugin::available_locations(battle::Role role) { return available_locations(get_role_location_type(role)); } @@ -1042,13 +1051,13 @@ std::vector asst::RoguelikeBattleTaskPlugin::available_locations(co return available_locations(get_oper_location_type(name)); } -std::vector asst::RoguelikeBattleTaskPlugin::available_locations(BattleLocationType type) +std::vector asst::RoguelikeBattleTaskPlugin::available_locations(battle::LocationType type) { std::vector result; for (const auto& [loc, tile] : m_normal_tile_info) { - bool position_mathced = tile.buildable == type || tile.buildable == BattleLocationType::All; - position_mathced |= (type == BattleLocationType::All) && (tile.buildable == BattleLocationType::Melee || - tile.buildable == BattleLocationType::Ranged); + bool position_mathced = tile.buildable == type || tile.buildable == battle::LocationType::All; + position_mathced |= (type == battle::LocationType::All) && (tile.buildable == battle::LocationType::Melee || + tile.buildable == battle::LocationType::Ranged); if (position_mathced && tile.key != TilePack::TileKey::DeepSea && // 水上要先放板子才能放人,肉鸽里也没板子,那就当作不可放置 !m_used_tiles.contains(loc) && !m_blacklist_location.contains(loc)) { @@ -1058,42 +1067,42 @@ std::vector asst::RoguelikeBattleTaskPlugin::available_locations(Ba return result; } -asst::BattleAttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(const BattleRealTimeOper& oper) +asst::battle::AttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(const battle::DeploymentOper& oper) { int64_t elite = status()->get_number(Status::RoguelikeCharElitePrefix + oper.name).value_or(0); - BattleAttackRange right_attack_range = BattleData.get_range(oper.name, elite); + battle::AttackRange right_attack_range = BattleData.get_range(oper.name, elite); if (right_attack_range == BattleDataConfig::EmptyRange) { switch (oper.role) { - case BattleRole::Support: + case battle::Role::Support: right_attack_range = { Point(-1, -1), Point(0, -1), Point(1, -1), Point(2, -1), // Point(-1, 0), Point(0, 0), Point(1, 0), Point(2, 0), // Point(-1, 1), Point(0, 1), Point(1, 1), Point(2, 1), // }; break; - case BattleRole::Caster: + case battle::Role::Caster: right_attack_range = { Point(0, -1), Point(1, -1), Point(2, -1), // Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0), // Point(0, 1), Point(1, 1), Point(2, 1), // }; break; - case BattleRole::Medic: - case BattleRole::Sniper: + case battle::Role::Medic: + case battle::Role::Sniper: right_attack_range = { Point(0, -1), Point(1, -1), Point(2, -1), Point(3, -1), // Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0), // Point(0, 1), Point(1, 1), Point(2, 1), Point(3, 1), // }; break; - case BattleRole::Warrior: + case battle::Role::Warrior: right_attack_range = { Point(0, 0), Point(1, 0), Point(2, 0) }; break; - case BattleRole::Special: - case BattleRole::Tank: - case BattleRole::Pioneer: - case BattleRole::Drone: + case battle::Role::Special: + case battle::Role::Tank: + case battle::Role::Pioneer: + case battle::Role::Drone: right_attack_range = { Point(0, 0), Point(1, 0) }; break; default: @@ -1105,7 +1114,7 @@ asst::BattleAttackRange asst::RoguelikeBattleTaskPlugin::get_attack_range(const } asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::calc_best_plan( - const BattleRealTimeOper& oper) + const battle::DeploymentOper& oper) { if (m_cur_home_index >= m_homes.size()) { m_cur_home_index = 0; @@ -1113,10 +1122,10 @@ asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::cal Point home(5, 5); // 实在找不到家门了,随便取个点当家门用算了,一般是地图的中间 Point recommended_direction; - static const std::unordered_map direction_map = { - { BattleDeployDirection::Up, Point::up() }, { BattleDeployDirection::Down, Point::down() }, - { BattleDeployDirection::Left, Point::left() }, { BattleDeployDirection::Right, Point::right() }, - { BattleDeployDirection::None, Point() }, + static const std::unordered_map direction_map = { + { battle::DeployDirection::Up, Point::up() }, { battle::DeployDirection::Down, Point::down() }, + { battle::DeployDirection::Left, Point::left() }, { battle::DeployDirection::Right, Point::right() }, + { battle::DeployDirection::None, Point() }, }; if (m_cur_home_index < m_homes.size()) { const auto& rp_home = m_homes.at(m_cur_home_index); @@ -1162,7 +1171,7 @@ asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::cal constexpr int DistWeights = -1050; int extra_dist = std::abs(loc.x - home.x) + std::abs(loc.y - home.y) - min_dist; int extra_dist_score = DistWeights * extra_dist; - if (oper.role == BattleRole::Medic) { // 医疗干员离得远无所谓 + if (oper.role == battle::Role::Medic) { // 医疗干员离得远无所谓 extra_dist_score = 0; } @@ -1181,8 +1190,8 @@ asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::cal } // 如果是医疗干员,判断覆盖范围内有无第一次放置的干员 - if (oper.role == BattleRole::Medic) { - BattleAttackRange right_attack_range = get_attack_range(oper); + if (oper.role == battle::Role::Medic) { + battle::AttackRange right_attack_range = get_attack_range(oper); for (const Point& direction : { Point::right(), Point::up(), Point::left(), Point::down() }) { if (direction == best_direction) break; for (Point& point : right_attack_range) @@ -1205,7 +1214,7 @@ asst::RoguelikeBattleTaskPlugin::DeployInfo asst::RoguelikeBattleTaskPlugin::cal } std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction_and_score( - Point loc, const BattleRealTimeOper& oper, Point recommended_direction) + Point loc, const battle::DeploymentOper& oper, Point recommended_direction) { LogTraceFunction; @@ -1233,12 +1242,12 @@ std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction } Point home_direction(-base_direction.x, -base_direction.y); // 医疗反着算 - if (oper.role == BattleRole::Medic) { + if (oper.role == battle::Role::Medic) { base_direction = -base_direction; } // 按朝右算,后面根据方向做转换 - BattleAttackRange right_attack_range = get_attack_range(oper); + battle::AttackRange right_attack_range = get_attack_range(oper); int max_score = 0; Point opt_direction; @@ -1269,10 +1278,10 @@ std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction }; switch (oper.role) { - case BattleRole::Medic: + case battle::Role::Medic: if (auto iter = m_used_tiles.find(absolute_pos); iter != m_used_tiles.cend() && - BattleData.get_role(iter->second) != BattleRole::Drone) // 根据哪个方向上人多决定朝向哪 + BattleData.get_role(iter->second) != battle::Role::Drone) // 根据哪个方向上人多决定朝向哪 score += 10000; if (auto iter = m_side_tile_info.find(absolute_pos); iter != m_side_tile_info.end()) score += TileKeyMedicWeights.at(iter->second.key); @@ -1288,11 +1297,11 @@ std::pair asst::RoguelikeBattleTaskPlugin::calc_best_direction score += 300; } - if (oper.role != BattleRole::Medic && direction == home_direction) { + if (oper.role != battle::Role::Medic && direction == home_direction) { score -= 500; } - if (oper.role != BattleRole::Medic && direction == recommended_direction) { + if (oper.role != battle::Role::Medic && direction == recommended_direction) { score += 2000; } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h index 66616d6e00..13c5ff5a08 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h +++ b/src/MaaCore/Task/Roguelike/RoguelikeBattleTaskPlugin.h @@ -5,13 +5,14 @@ #include "Common/AsstBattleDef.h" #include "Common/AsstTypes.h" -#include "Vision/Miscellaneous/BattleImageAnalyzer.h" #include "Config/Miscellaneous/TilePack.h" #include "Task/AbstractTaskPlugin.h" +#include "Task/BattleHelper.h" +#include "Vision/Miscellaneous/BattleImageAnalyzer.h" namespace asst { - class RoguelikeBattleTaskPlugin : public AbstractTaskPlugin + class RoguelikeBattleTaskPlugin : public AbstractTaskPlugin, public BattleHelper { using Time_Point = std::chrono::time_point; @@ -19,7 +20,7 @@ namespace asst inline static const std::string UnknownName = "Unknown"; public: - using AbstractTaskPlugin::AbstractTaskPlugin; + RoguelikeBattleTaskPlugin(const AsstCallback& callback, Assistant* inst, std::string_view task_chain); virtual ~RoguelikeBattleTaskPlugin() override = default; virtual bool verify(AsstMsg msg, const json::value& details) const override; @@ -32,14 +33,14 @@ namespace asst // 有些特殊的角色,他的职业并不一定和正常的位置相对应,比如“掠风”是地面辅助 // get_role_position 可以仅知道干员职业的情况下,大概猜测一下位置 // get_oper_position 可以在已知干员名的时候获得准确的位置 - BattleLocationType get_role_location_type(const BattleRole& role); - BattleLocationType get_oper_location_type(const std::string& name); + battle::LocationType get_role_location_type(const battle::Role& role); + battle::LocationType get_oper_location_type(const std::string& name); - std::vector available_locations(BattleRole role); + std::vector available_locations(battle::Role role); std::vector available_locations(const std::string& name); - std::vector available_locations(BattleLocationType type); + std::vector available_locations(battle::LocationType type); - BattleOperPosition get_role_position(const BattleRole& role); + battle::OperPosition get_role_position(const battle::Role& role); void wait_for_start(); bool get_stage_info(); @@ -57,23 +58,23 @@ namespace asst bool cancel_oper_selection(); bool is_oper_name_error(const std::string& name); - void set_position_full(const BattleLocationType& loc_type, bool full); + void set_position_full(const battle::LocationType& loc_type, bool full); void set_position_full(const Point& point, bool full); - void set_position_full(const BattleRole& role, bool full); + void set_position_full(const battle::Role& role, bool full); void set_position_full(const std::string& name, bool full); - bool get_position_full(const BattleRole& role); + bool get_position_full(const battle::Role& role); struct DeployInfo { Point placed; Point direction; }; - BattleAttackRange get_attack_range(const BattleRealTimeOper& oper); - DeployInfo calc_best_plan(const BattleRealTimeOper& oper); + battle::AttackRange get_attack_range(const battle::DeploymentOper& oper); + DeployInfo calc_best_plan(const battle::DeploymentOper& oper); // 计算摆放干员的朝向 // 返回滑动的方向、得分 - std::pair calc_best_direction_and_score(Point loc, const BattleRealTimeOper& oper, + std::pair calc_best_direction_and_score(Point loc, const battle::DeploymentOper& oper, Point recommended_direction); struct DroneTile @@ -123,10 +124,10 @@ namespace asst size_t m_cur_home_index = 0; cv::Mat m_dice_image; - std::array m_role_order; + std::array m_role_order; std::unordered_map m_side_tile_info; std::unordered_map m_normal_tile_info; - std::vector m_homes; + std::vector m_homes; std::vector m_wait_blocking; std::vector m_wait_medic; std::vector m_indeed_no_medic; @@ -140,7 +141,7 @@ namespace asst std::unordered_map m_opers_in_field; std::unordered_map m_restore_status; std::priority_queue m_need_clear_tiles; - std::unordered_map m_force_deploy_direction; + std::unordered_map m_force_deploy_direction; std::string m_stage_name; }; diff --git a/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp index 1c6b5f1048..009da15caa 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeCustomStartTaskPlugin.cpp @@ -115,10 +115,10 @@ bool asst::RoguelikeCustomStartTaskPlugin::hijack_core_char() { const std::string& char_name = m_customs[RoguelikeCustomType::CoreChar]; - static const std::unordered_map RoleOcrNameMap = { - { BattleRole::Caster, "术师" }, { BattleRole::Medic, "医疗" }, { BattleRole::Pioneer, "先锋" }, - { BattleRole::Sniper, "狙击" }, { BattleRole::Special, "特种" }, { BattleRole::Support, "辅助" }, - { BattleRole::Tank, "重装" }, { BattleRole::Warrior, "近卫" } + static const std::unordered_map RoleOcrNameMap = { + { battle::Role::Caster, "术师" }, { battle::Role::Medic, "医疗" }, { battle::Role::Pioneer, "先锋" }, + { battle::Role::Sniper, "狙击" }, { battle::Role::Special, "特种" }, { battle::Role::Support, "辅助" }, + { battle::Role::Tank, "重装" }, { battle::Role::Warrior, "近卫" } }; const auto& role = BattleData.get_role(char_name); auto role_iter = RoleOcrNameMap.find(role); diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp index 81428dc988..c3da84b04c 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.cpp @@ -12,6 +12,8 @@ #include "Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h" #include "Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h" +using namespace asst::battle::roguelike; + bool asst::RoguelikeRecruitTaskPlugin::verify(AsstMsg msg, const json::value& details) const { if (msg != AsstMsg::SubTaskCompleted || details.get("subtask", std::string()) != "ProcessTask") { @@ -37,7 +39,7 @@ bool asst::RoguelikeRecruitTaskPlugin::verify(AsstMsg msg, const json::value& de } } -asst::BattleRole asst::RoguelikeRecruitTaskPlugin::get_oper_role(const std::string& name) +asst::battle::Role asst::RoguelikeRecruitTaskPlugin::get_oper_role(const std::string& name) { return BattleData.get_role(name); } @@ -45,9 +47,9 @@ asst::BattleRole asst::RoguelikeRecruitTaskPlugin::get_oper_role(const std::stri bool asst::RoguelikeRecruitTaskPlugin::is_oper_melee(const std::string& name) { const auto role = get_oper_role(name); - if (role != BattleRole::Pioneer && role != BattleRole::Tank && role != BattleRole::Warrior) return false; + if (role != battle::Role::Pioneer && role != battle::Role::Tank && role != battle::Role::Warrior) return false; const auto loc = BattleData.get_location_type(name); - return loc == BattleLocationType::Melee; + return loc == battle::LocationType::Melee; } bool asst::RoguelikeRecruitTaskPlugin::_run() @@ -69,7 +71,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() bool recruited = false; - auto recruit_oper = [&](const BattleRecruitOperInfo& info) { + auto recruit_oper = [&](const battle::roguelike::Recruitment& info) { select_oper(info); recruited = true; }; @@ -81,11 +83,11 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() std::string str_chars_info = status()->get_str(Status::RoguelikeCharOverview).value_or(json::value().to_string()); json::value json_chars_info = json::parse(str_chars_info).value_or(json::value()); const auto& chars_map = json_chars_info.as_object(); - std::unordered_map team_roles; + std::unordered_map team_roles; int offset_melee_num = 0; for (const auto& oper : chars_map) { if (oper.first.starts_with("预备干员")) continue; - team_roles[get_role_type(oper.first)]++; + team_roles[battle::get_role_type(oper.first)]++; if (is_oper_melee(oper.first)) offset_melee_num++; } // 候选干员 @@ -191,7 +193,7 @@ bool asst::RoguelikeRecruitTaskPlugin::_run() } if (!recruit_info.is_alternate) { - const BattleRole oper_role = get_oper_role(oper_info.name); + const battle::Role oper_role = get_oper_role(oper_info.name); int role_num = recruit_info.offset_melee ? offset_melee_num : team_roles[oper_role]; for (const auto& offset_pair : ranges::reverse_view(recruit_info.recruit_priority_offset)) { if (role_num >= offset_pair.first) { @@ -342,11 +344,11 @@ bool asst::RoguelikeRecruitTaskPlugin::check_char(const std::string& char_name, if (analyzer.analyze()) { const auto& chars = analyzer.get_result(); auto it = ranges::find_if( - chars, [&](const BattleRecruitOperInfo& oper) -> bool { return oper.name == char_name; }); + chars, [&](const battle::roguelike::Recruitment& oper) -> bool { return oper.name == char_name; }); std::unordered_set oper_names; ranges::transform(chars, std::inserter(oper_names, oper_names.end()), - std::mem_fn(&BattleRecruitOperInfo::name)); + std::mem_fn(&battle::roguelike::Recruitment::name)); Log.info(__FUNCTION__, "| Oper list:", oper_names); if (it != chars.cend()) { @@ -402,31 +404,30 @@ bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& nam // 判断是否存在“选择助战”按钮,存在则点击 auto screen_choose = ctrler()->get_image(); RoguelikeRecruitSupportAnalyzer analyzer_choose(screen_choose); - analyzer_choose.set_mode(RoguelikeSupportAnalyzeMode::ChooseSupportBtn); + analyzer_choose.set_mode(SupportAnalyzeMode::ChooseSupportBtn); if (!analyzer_choose.analyze()) { // 非开局招募,无助战按钮 return false; } const auto& choose_btn_rect = analyzer_choose.get_result_choose_support(); Log.info(__FUNCTION__, "| check choose support btn ", choose_btn_rect); ctrler()->click(choose_btn_rect); - ProcessTask(*this, { "RoguelikeRecruitSupportEnterFlag" }).run(); // 等待页面加载 - + ProcessTask(*this, { "RoguelikeRecruitSupportEnterFlag" }).run(); // 等待页面加载 // 识别所有干员,应该最多两页 const int SwipeTimes = 1; - std::vector satisfied_chars; + std::vector satisfied_chars; for (int retry = 0; retry <= max_refresh; ++retry) { for (int swipe = 0; swipe < SwipeTimes; ++swipe) { auto screen_char = ctrler()->get_image(); RoguelikeRecruitSupportAnalyzer analyzer_char(screen_char); - analyzer_char.set_mode(RoguelikeSupportAnalyzeMode::AnalyzeChars); + analyzer_char.set_mode(SupportAnalyzeMode::AnalyzeChars); analyzer_char.set_required({ name }); if (analyzer_char.analyze()) { auto& chars_page = analyzer_char.get_result_char(); bool use_nonfriend_support = get_status_bool(Status::RoguelikeUseNonfriendSupport); - auto check_satisfiy = [&use_nonfriend_support](const RoguelikeRecruitSupportCharInfo& chara) { + auto check_satisfiy = [&use_nonfriend_support](const RecruitSupportCharInfo& chara) { return chara.is_friend || use_nonfriend_support; }; std::copy_if(chars_page.begin(), chars_page.end(), @@ -442,7 +443,7 @@ bool asst::RoguelikeRecruitTaskPlugin::check_support_char(const std::string& nam if (retry >= max_refresh) break; auto screen_refresh = ctrler()->get_image(); RoguelikeRecruitSupportAnalyzer analyzer_refresh(screen_refresh); - analyzer_refresh.set_mode(RoguelikeSupportAnalyzeMode::RefreshSupportBtn); + analyzer_refresh.set_mode(SupportAnalyzeMode::RefreshSupportBtn); if (!analyzer_refresh.analyze()) { click_return_button(); return false; @@ -481,7 +482,7 @@ bool asst::RoguelikeRecruitTaskPlugin::check_core_char() return check_char(core_opt.value()); } -void asst::RoguelikeRecruitTaskPlugin::select_oper(const BattleRecruitOperInfo& oper) +void asst::RoguelikeRecruitTaskPlugin::select_oper(const battle::roguelike::Recruitment& oper) { Log.info(__FUNCTION__, "| Choose oper:", oper.name, "( elite", oper.elite, "level", oper.level, ")"); @@ -499,10 +500,9 @@ void asst::RoguelikeRecruitTaskPlugin::select_oper(const BattleRecruitOperInfo& status()->set_str(Status::RoguelikeCharOverview, overview.to_string()); } -inline bool asst::RoguelikeRecruitTaskPlugin::get_status_bool(const std::string& key) +bool asst::RoguelikeRecruitTaskPlugin::get_status_bool(const std::string& key) { - auto value_opt = status()->get_str(key); - return value_opt && value_opt->size() == 1 && value_opt.value()[0] == '1'; + return status()->get_str(key).value_or("") == "1"; } void asst::RoguelikeRecruitTaskPlugin::swipe_to_the_left_of_operlist(int loop_times) diff --git a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h index d366343b3b..83cd50b408 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h +++ b/src/MaaCore/Task/Roguelike/RoguelikeRecruitTaskPlugin.h @@ -33,7 +33,7 @@ namespace asst bool check_support_char(const std::string& name, const int max_refresh); bool check_core_char(); - BattleRole get_oper_role(const std::string& name); + battle::Role get_oper_role(const std::string& name); bool is_oper_melee(const std::string& name); // 招募指定干员 // @@ -42,8 +42,7 @@ namespace asst // is_rtl: 滑动方向 (true: 从右向左; false: 从左向右,需要先滑动到最左侧) // 返回值: 招募结果 (true: 成功; false: 失败) bool check_char(const std::string& char_name, bool is_rtl = false); - void select_oper(const BattleRecruitOperInfo& oper); - - inline bool get_status_bool(const std::string& key); + void select_oper(const battle::roguelike::Recruitment& oper); + bool get_status_bool(const std::string& key); }; } diff --git a/src/MaaCore/Task/Roguelike/RoguelikeShoppingTaskPlugin.cpp b/src/MaaCore/Task/Roguelike/RoguelikeShoppingTaskPlugin.cpp index c50ba68e44..d5e961f062 100644 --- a/src/MaaCore/Task/Roguelike/RoguelikeShoppingTaskPlugin.cpp +++ b/src/MaaCore/Task/Roguelike/RoguelikeShoppingTaskPlugin.cpp @@ -50,8 +50,8 @@ bool asst::RoguelikeShoppingTaskPlugin::_run() std::string str_chars_info = status()->get_str(Status::RoguelikeCharOverview).value_or(json::value().to_string()); json::value json_chars_info = json::parse(str_chars_info).value_or(json::value()); - std::unordered_map map_roles_count; - std::unordered_map map_wait_promotion; + std::unordered_map map_roles_count; + std::unordered_map map_wait_promotion; size_t total_wait_promotion = 0; std::unordered_set chars_list; for (auto& [name, json_info] : json_chars_info.as_object()) { @@ -67,16 +67,16 @@ bool asst::RoguelikeShoppingTaskPlugin::_run() chars_list.emplace(name); if (name == "阿米娅") { - map_roles_count[BattleRole::Caster] += 1; - map_roles_count[BattleRole::Warrior] += 1; + map_roles_count[battle::Role::Caster] += 1; + map_roles_count[battle::Role::Warrior] += 1; if (elite == 1 && level == 70) { total_wait_promotion += 1; - map_wait_promotion[BattleRole::Caster] += 1; - map_wait_promotion[BattleRole::Warrior] += 1; + map_wait_promotion[battle::Role::Caster] += 1; + map_wait_promotion[battle::Role::Warrior] += 1; } } else { - BattleRole role = BattleData.get_role(name); + battle::Role role = BattleData.get_role(name); map_roles_count[role] += 1; static const std::unordered_map RarityPromotionLevel = { diff --git a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp index f4e86664ff..118622ce59 100644 --- a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp @@ -7,11 +7,11 @@ #include "Config/TaskData.h" #include "Config/TemplResource.h" +#include "Utils/Logger.hpp" #include "Vision/HashImageAnalyzer.h" #include "Vision/MatchImageAnalyzer.h" #include "Vision/MultiMatchImageAnalyzer.h" #include "Vision/OcrWithFlagTemplImageAnalyzer.h" -#include "Utils/Logger.hpp" bool asst::BattleImageAnalyzer::set_target(int target) { @@ -59,7 +59,7 @@ bool asst::BattleImageAnalyzer::analyze() return ret; } -const std::vector& asst::BattleImageAnalyzer::get_opers() const noexcept +const std::vector& asst::BattleImageAnalyzer::get_opers() const noexcept { return m_opers; } @@ -124,7 +124,7 @@ bool asst::BattleImageAnalyzer::opers_analyze() size_t index = 0; for (const MatchRect& flag_mrect : flags_analyzer.get_result()) { - BattleRealTimeOper oper; + battle::DeploymentOper oper; oper.rect = flag_mrect.rect.move(click_move); if (oper.rect.x + oper.rect.width >= m_image.cols) { oper.rect.width = m_image.cols - oper.rect.x; @@ -166,17 +166,17 @@ bool asst::BattleImageAnalyzer::opers_analyze() return true; } -asst::BattleRole asst::BattleImageAnalyzer::oper_role_analyze(const Rect& roi) +asst::battle::Role asst::BattleImageAnalyzer::oper_role_analyze(const Rect& roi) { - static const std::unordered_map RolesName = { - { BattleRole::Caster, "Caster" }, { BattleRole::Medic, "Medic" }, { BattleRole::Pioneer, "Pioneer" }, - { BattleRole::Sniper, "Sniper" }, { BattleRole::Special, "Special" }, { BattleRole::Support, "Support" }, - { BattleRole::Tank, "Tank" }, { BattleRole::Warrior, "Warrior" }, { BattleRole::Drone, "Drone" } + static const std::unordered_map RolesName = { + { battle::Role::Caster, "Caster" }, { battle::Role::Medic, "Medic" }, { battle::Role::Pioneer, "Pioneer" }, + { battle::Role::Sniper, "Sniper" }, { battle::Role::Special, "Special" }, { battle::Role::Support, "Support" }, + { battle::Role::Tank, "Tank" }, { battle::Role::Warrior, "Warrior" }, { battle::Role::Drone, "Drone" } }; MatchImageAnalyzer role_analyzer(m_image); - auto result = BattleRole::Unknown; + auto result = battle::Role::Unknown; double max_score = 0; for (auto&& [role, role_name] : RolesName) { role_analyzer.set_task_info("BattleOperRole" + role_name); diff --git a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h index caf43a87d5..e17170f317 100644 --- a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.h @@ -11,10 +11,10 @@ namespace asst enum Target // 需要识别的目标 { None = 0, - HP = 1, // 剩余生命值 - Home = 2, // 蓝色的家门 - Oper = 4, // 下方的干员信息 - //Skill = 8, // cd 转好了可以使用的技能 + HP = 1, // 剩余生命值 + Home = 2, // 蓝色的家门 + Oper = 4, // 下方的干员信息 + // Skill = 8, // cd 转好了可以使用的技能 Kills = 16, // 击杀数 Cost = 32, // 费用 Vacancies = 64, // 剩余可部署干员数 @@ -30,7 +30,7 @@ namespace asst void set_pre_total_kills(int pre_total_kills); virtual bool analyze() override; - virtual const std::vector& get_opers() const noexcept; + virtual const std::vector& get_opers() const noexcept; virtual const std::vector& get_homes() const noexcept; int get_hp() const noexcept; @@ -43,13 +43,13 @@ namespace asst protected: bool opers_analyze(); // 识别干员 - BattleRole oper_role_analyze(const Rect& roi); + battle::Role oper_role_analyze(const Rect& roi); bool oper_cooling_analyze(const Rect& roi); int oper_cost_analyze(const Rect& roi); bool oper_available_analyze(const Rect& roi); - bool home_analyze(); // 识别蓝色的家门 - //bool skill_analyze(); // 识别技能是否可用 + bool home_analyze(); // 识别蓝色的家门 + // bool skill_analyze(); // 识别技能是否可用 bool hp_analyze(); // 识别剩余生命值 bool kills_analyze(); // 识别击杀数 bool cost_analyze(); // 识别费用 @@ -60,12 +60,12 @@ namespace asst int m_pre_total_kills = 0; // 之前的击杀总数,因为击杀数经常识别不准所以依赖外部传入作为参考 /* 识别结果缓存 */ - std::vector m_opers; // 下方干员信息 - std::vector m_homes; // 蓝色的家门位置 - std::vector m_ready_skills; // 可以释放的技能(Rect实际是干员的位置) - int m_hp = 0; // 剩余生命值 - int m_kills = 0; // 击杀数 - int m_total_kills = 0; // 击杀总数 - int m_cost = 0; // 部署费用 + std::vector m_opers; // 下方干员信息 + std::vector m_homes; // 蓝色的家门位置 + std::vector m_ready_skills; // 可以释放的技能(Rect实际是干员的位置) + int m_hp = 0; // 剩余生命值 + int m_kills = 0; // 击杀数 + int m_total_kills = 0; // 击杀总数 + int m_cost = 0; // 部署费用 }; } // namespace asst diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp index bb44f06978..d299ca5d2c 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.cpp @@ -1,10 +1,10 @@ #include "RoguelikeRecruitImageAnalyzer.h" -#include "Vision/MatchImageAnalyzer.h" -#include "Vision/OcrWithFlagTemplImageAnalyzer.h" #include "Config/TaskData.h" #include "Utils/Logger.hpp" #include "Utils/NoWarningCV.h" +#include "Vision/MatchImageAnalyzer.h" +#include "Vision/OcrWithFlagTemplImageAnalyzer.h" bool asst::RoguelikeRecruitImageAnalyzer::analyze() { @@ -37,7 +37,7 @@ bool asst::RoguelikeRecruitImageAnalyzer::analyze() continue; } - BattleRecruitOperInfo info; + battle::roguelike::Recruitment info; info.rect = rect; info.name = name; info.elite = elite; diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h index 63d7a30b01..7938e8e1f4 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitImageAnalyzer.h @@ -19,6 +19,6 @@ namespace asst int match_elite(const Rect& raw_roi); static int match_level(const cv::Mat& image, const Rect& raw_roi); - std::vector m_result; + std::vector m_result; }; } diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp index a6ec0e5370..0f1dd2440c 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.cpp @@ -16,7 +16,7 @@ bool asst::RoguelikeRecruitSupportAnalyzer::analyze() { LogTraceFunction; - if (m_mode == RoguelikeSupportAnalyzeMode::ChooseSupportBtn) { + if (m_mode == battle::roguelike::SupportAnalyzeMode::ChooseSupportBtn) { // 识别“选择助战” OcrImageAnalyzer analyzer(m_image); const auto& task = Task.get("RoguelikeChooseSupportBtnOcr"); @@ -27,7 +27,7 @@ bool asst::RoguelikeRecruitSupportAnalyzer::analyze() Log.info(__FUNCTION__, "| ChooseSupportBtn"); return true; } - else if (m_mode == RoguelikeSupportAnalyzeMode::AnalyzeChars) { + else if (m_mode == battle::roguelike::SupportAnalyzeMode::AnalyzeChars) { // 识别干员 OcrImageAnalyzer analyzer(m_image); analyzer.set_roi(Task.get("RoguelikeRecruitSupportOcr")->roi); @@ -60,7 +60,7 @@ bool asst::RoguelikeRecruitSupportAnalyzer::analyze() Rect elite_roi = name_rect.move(task_off_elite->rect_move); int char_elite = match_elite(elite_roi, task_off_elite->special_params.front()); - RoguelikeRecruitSupportCharInfo char_info { + battle::roguelike::RecruitSupportCharInfo char_info { { char_rect.text, char_rect.rect, char_elite, char_level }, is_friend, char_elite, char_level }; @@ -87,7 +87,7 @@ bool asst::RoguelikeRecruitSupportAnalyzer::analyze() } return !m_char_result.empty(); } - else if (m_mode == RoguelikeSupportAnalyzeMode::RefreshSupportBtn) { + else if (m_mode == battle::roguelike::SupportAnalyzeMode::RefreshSupportBtn) { // 识别“更新助战列表” OcrImageAnalyzer analyzer(m_image); const auto& task = Task.get("RoguelikeRefreshSupportBtnOcr"); diff --git a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h index bb2e319716..0fbcd83151 100644 --- a/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h +++ b/src/MaaCore/Vision/Roguelike/RoguelikeRecruitSupportAnalyzer.h @@ -13,7 +13,7 @@ namespace asst bool analyze() override; - void set_mode(RoguelikeSupportAnalyzeMode mode) noexcept { m_mode = mode; } + void set_mode(battle::roguelike::SupportAnalyzeMode mode) noexcept { m_mode = mode; } void set_required(std::vector required) noexcept { m_required = required; } const auto& get_result_choose_support() { return m_choose_support_result; } @@ -21,10 +21,10 @@ namespace asst const auto& get_result_refresh() { return m_refresh_result; } private: - RoguelikeSupportAnalyzeMode m_mode; + battle::roguelike::SupportAnalyzeMode m_mode = battle::roguelike::SupportAnalyzeMode::ChooseSupportBtn; Rect m_choose_support_result; - RoguelikeRefreshSupportInfo m_refresh_result; - std::vector m_char_result; + battle::roguelike::RefreshSupportInfo m_refresh_result; + std::vector m_char_result; std::vector m_required; int judge_is_friend(const Rect& roi, const double r_threshold);