From 57b21b06fcd9cd25bf2c157ac1dec24c85bf846e Mon Sep 17 00:00:00 2001 From: MistEO Date: Sun, 3 Apr 2022 22:56:19 +0800 Subject: [PATCH] =?UTF-8?q?feat.=E8=82=89=E9=B8=BD=E6=96=B0=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E7=94=BB=E9=A5=BC=EF=BC=8C=E5=AE=8C=E6=88=90=E4=BA=86?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E9=85=8D=E7=BD=AE=E9=83=A8=E5=88=86=E7=9A=84?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/肉鸽战斗流程协议.md | 65 +++++++ resource/copilot/WD-EX-1.json | 54 ++++++ resource/roguelike/意外.json | 28 +++ resource/tasks.json | 4 +- src/MeoAssistant/AbstractConfiger.cpp | 11 +- src/MeoAssistant/AsstBattleDef.h | 16 +- src/MeoAssistant/BattleProcessTask.cpp | 7 +- src/MeoAssistant/BattleProcessTask.h | 2 +- src/MeoAssistant/MeoAssistant.vcxproj | 2 + src/MeoAssistant/MeoAssistant.vcxproj.filters | 6 + src/MeoAssistant/Resource.cpp | 11 ++ src/MeoAssistant/Resource.h | 10 ++ .../RoguelikeBattleTaskPlugin.cpp | 2 +- src/MeoAssistant/RoguelikeCopilotConfiger.cpp | 161 ++++++++++++++++++ src/MeoAssistant/RoguelikeCopilotConfiger.h | 26 +++ 15 files changed, 396 insertions(+), 9 deletions(-) create mode 100644 docs/肉鸽战斗流程协议.md create mode 100644 resource/copilot/WD-EX-1.json create mode 100644 resource/roguelike/意外.json create mode 100644 src/MeoAssistant/RoguelikeCopilotConfiger.cpp create mode 100644 src/MeoAssistant/RoguelikeCopilotConfiger.h diff --git a/docs/肉鸽战斗流程协议.md b/docs/肉鸽战斗流程协议.md new file mode 100644 index 0000000000..2f643213ac --- /dev/null +++ b/docs/肉鸽战斗流程协议.md @@ -0,0 +1,65 @@ +# 肉鸽战斗流程协议 + +`resource/roguelike/*.json` 的使用方法及各字段说明 + +## 完整字段一览 + +```jsonc +{ + "stage_name": "驯兽小屋", // 关卡名,必选 + "actions": [ // 战斗中的操作。有序,执行完前一个才会去执行下一个。必选 + { + "type": "部署", // 操作类型,可选,默认 "Deploy" + // "Deploy" | "Skill" | "Retreat" | "AllSkill" + // "部署" | "技能" | "撤退" | "全部技能" + // 中英文皆可,效果相同 + // 若为 "部署", 当费用不够时,会一直等待到费用够 + // 若为 "技能", 当技能 cd 没转好时,会直接跳过当前项。 + // 考虑不同干员技能效果及 cd 差异巨大,谨慎使用(例如 山 2 技能 这种,假如已经开了,你又开一次,就给关了) + // "全部技能", 会一次性使用所有 skill_usgae == 3 的干员的技能 + + "kills": 0, // 击杀数条件,如果没达到就一直等待。可选,默认为 0。直接执行 + // TODO: 其他条件 + // TODO: "condition_type": 0, // 执行条件间的关系,可选,默认 0 + // // 0 - 且; 1 - 或 + + "roles": [ // 该位置使用的职业。可选,默认为空,任何职业都可以 + // 有序,会先使用靠前的职业 + // 会根据位置类型自动区分是高台还是地面。若设置了不可放置的职业,则忽略该职业 + // 如果这里面的职业一个都没有,则会跳过该位置转而执行下一条 + "近卫", + "先锋", + "重装", + "召唤物" + ], + + "waiting_cost": false, // 如果有 roles 中有靠前的职业,但费用不够,是否等待。可选,默认 false + // 为 true 时,会使用 roles 中,当前有的、最靠前的职业(一直等他费用够) + // 为 false 时,会使用 roles 中,当前有的、费用够的中最靠前的职业 + + "location": [ 5, 5 ], // 操作的干员的位置。必选 + + "direction": "左", // 部署干员的干员朝向。 type 为 "部署" 时必选 + // "Left" | "Right" | "Up" | "Down" | "None" + // "左" | "右" | "上" | "下" | "无" + // 中英文皆可,效果相同 + + "pre_delay": 0, // 前置延时。可选,默认 0, 单位毫秒 + "rear_delay": 0, // 后置延时。可选,默认 0, 单位毫秒 + + "timeout": 999999999 // 超时时间。当 type 为 "部署" 时可选。默认 INT_MAX, 单位毫秒 + // 等待超时则放弃当前动作, 转而执行下一个动作 + }, + { // 简单举例 + "location": [6, 6], + "roles": [ + "近卫", + "先锋", + "重装", + "召唤物" + ], + "direction": "左" + } + ] +} +``` diff --git a/resource/copilot/WD-EX-1.json b/resource/copilot/WD-EX-1.json new file mode 100644 index 0000000000..e44d455196 --- /dev/null +++ b/resource/copilot/WD-EX-1.json @@ -0,0 +1,54 @@ +{ + "minimum_required": "v4.0", + "desc": { + "title": "《摆完挂机 简单好抄》", + "details": "作业抄自 https://www.bilibili.com/video/BV12r4y1W7s4?p=10", + "strategy_author": "萧然Q", + "copilot_author": "Evil Deamo", + "version": "v1.0" + }, + "stage_name": "兽群战术", + "opers": [ + { + "name": "煌", + "skill": 2 + } + ], + "groups": [ + { + "name": "普通单奶", + "opers": [ + { + "name": "闪灵", + "skill": 2 + }, + { + "name": "华法琳", + "skill": 1 + }, + { + "name": "赫默2", + "skill": 1 + } + ] + } + ], + "actions": [ + { + "name": "煌", + "location": [ + 7, + 3 + ], + "direction": "左" + }, + { + "name": "普通单奶", + "location": [ + 9, + 4 + ], + "direction": "左" + } + ] +} \ No newline at end of file diff --git a/resource/roguelike/意外.json b/resource/roguelike/意外.json new file mode 100644 index 0000000000..6bc59c6a9d --- /dev/null +++ b/resource/roguelike/意外.json @@ -0,0 +1,28 @@ +{ + "stage_name": "意外", + "actions": [ + { + "location": [ + 5, + 3 + ], + "roles": [ + "近卫", + "先锋", + "重装", + "召唤物" + ], + "direction": "左" + }, + { + "location": [ + 5, + 2 + ], + "roles": [ + "辅助" + ], + "direction": "左" + } + ] +} diff --git a/resource/tasks.json b/resource/tasks.json index a2223adc14..3e299d369a 100644 --- a/resource/tasks.json +++ b/resource/tasks.json @@ -3923,9 +3923,7 @@ 100, 255 ], - "templThreshold": 0.95, - "preDelay": 3000, - "rearDelay": 3000 + "templThreshold": 0.95 }, "BattleSkillReady": { "roi": [ diff --git a/src/MeoAssistant/AbstractConfiger.cpp b/src/MeoAssistant/AbstractConfiger.cpp index ffaf6df7ac..fe0887b484 100644 --- a/src/MeoAssistant/AbstractConfiger.cpp +++ b/src/MeoAssistant/AbstractConfiger.cpp @@ -9,11 +9,18 @@ bool asst::AbstractConfiger::load(const std::string& filename) { LogTraceFunction; - if (!std::filesystem::exists(filename)) { +#ifdef WIN32 + std::string cvt_filename = utils::utf8_to_gbk(filename); +#else + std::string cvt_filename = filename; +#endif + Log.info("Load:", cvt_filename); + + if (!std::filesystem::exists(cvt_filename)) { return false; } - std::string content = utils::load_file_without_bom(filename); + std::string content = utils::load_file_without_bom(cvt_filename); auto&& ret = json::parser::parse(content); if (!ret) { diff --git a/src/MeoAssistant/AsstBattleDef.h b/src/MeoAssistant/AsstBattleDef.h index 92340acf66..a2b50fdeb4 100644 --- a/src/MeoAssistant/AsstBattleDef.h +++ b/src/MeoAssistant/AsstBattleDef.h @@ -47,7 +47,8 @@ namespace asst Retreat, // 撤退干员 SkillUsage, // 技能用法 SwitchSpeed, // 切换二倍速 - BulletTime // 使用 1/5 的速度(点击任意干员),会在下一个任意操作后恢复原速度 + BulletTime, // 使用 1/5 的速度(点击任意干员),会在下一个任意操作后恢复原速度 + UseAllSkill // 使用所有技能,仅肉鸽模式 }; struct BattleAction // 操作 @@ -98,4 +99,17 @@ namespace asst return name == oper_name; } }; + + struct RoguelikeBattleAction + { + int kills = 0; + BattleActionType type = BattleActionType::Deploy; + std::vector roles; + bool waiting_cost = false; + Point location; + BattleDeployDirection direction = BattleDeployDirection::Right; + int pre_delay = 0; + int rear_delay = 0; + int time_out = INT_MAX; + }; } diff --git a/src/MeoAssistant/BattleProcessTask.cpp b/src/MeoAssistant/BattleProcessTask.cpp index 04bdfadfd8..6b03ca7db4 100644 --- a/src/MeoAssistant/BattleProcessTask.cpp +++ b/src/MeoAssistant/BattleProcessTask.cpp @@ -79,7 +79,12 @@ bool asst::BattleProcessTask::analyze_opers_preview() 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); } - cv::imwrite(utils::utf8_to_gbk(m_stage_name) + ".png", draw); +#ifdef WIN32 + std::string output_filename = utils::utf8_to_gbk(m_stage_name); +#else + std::string output_filename = m_stage_name; +#endif + cv::imwrite(output_filename + ".png", draw); //#endif // 干员头像出来之后,还要过 2 秒左右才可以点击,这里要加个延时 diff --git a/src/MeoAssistant/BattleProcessTask.h b/src/MeoAssistant/BattleProcessTask.h index c92bc5ae17..4cd0eff0e0 100644 --- a/src/MeoAssistant/BattleProcessTask.h +++ b/src/MeoAssistant/BattleProcessTask.h @@ -19,7 +19,7 @@ namespace asst protected: virtual bool _run() override; - bool get_stage_info(); + virtual bool get_stage_info(); bool battle_pause(); bool battle_speedup(); bool cancel_selection(); // 取消选择干员 diff --git a/src/MeoAssistant/MeoAssistant.vcxproj b/src/MeoAssistant/MeoAssistant.vcxproj index 36354b791c..8f66427041 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj +++ b/src/MeoAssistant/MeoAssistant.vcxproj @@ -73,6 +73,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/src/MeoAssistant/MeoAssistant.vcxproj.filters b/src/MeoAssistant/MeoAssistant.vcxproj.filters index 139ae2f820..edecf4fcce 100644 --- a/src/MeoAssistant/MeoAssistant.vcxproj.filters +++ b/src/MeoAssistant/MeoAssistant.vcxproj.filters @@ -318,6 +318,9 @@ 头文件\Resource + + 头文件\Resource + @@ -533,6 +536,9 @@ 源文件\Resource + + 源文件\Resource + diff --git a/src/MeoAssistant/Resource.cpp b/src/MeoAssistant/Resource.cpp index 8907b76093..7bec4e1df8 100644 --- a/src/MeoAssistant/Resource.cpp +++ b/src/MeoAssistant/Resource.cpp @@ -21,6 +21,7 @@ bool asst::Resource::load(const std::string& dir) constexpr static const char* ItemCfgFilename = "item_index.json"; constexpr static const char* InfrastCfgFilename = "infrast.json"; constexpr static const char* CopilotCfgDirname = "copilot"; + constexpr static const char* RoguelikeCfgDirname = "roguelike"; constexpr static const char* OcrResourceFilename = "PaddleOCR"; constexpr static const char* PenguinResourceFilename = "penguin-stats-recognize"; constexpr static const char* TilesCalcResourceFilename = "Arknights-Tile-Pos"; @@ -88,6 +89,16 @@ bool asst::Resource::load(const std::string& dir) } } + for (const auto& entry : std::filesystem::directory_iterator(dir + RoguelikeCfgDirname)) { + if (entry.path().extension() != ".json") { + continue; + } + if (!m_roguelike_cfg_unique_ins.load(entry.path().u8string())) { + m_last_error = entry.path().u8string() + " Load failed"; + return false; + } + } + if (!m_infrast_cfg_unique_ins.load(dir + InfrastCfgFilename)) { if (!m_loaded) { m_last_error = std::string(InfrastCfgFilename) + ": " + m_infrast_cfg_unique_ins.get_last_error(); diff --git a/src/MeoAssistant/Resource.h b/src/MeoAssistant/Resource.h index ccdd1f7c74..ff6d5ceff0 100644 --- a/src/MeoAssistant/Resource.h +++ b/src/MeoAssistant/Resource.h @@ -16,6 +16,7 @@ #include "TemplResource.h" #include "TilePack.h" #include "CopilotConfiger.h" +#include "RoguelikeCopilotConfiger.h" namespace asst { @@ -60,6 +61,10 @@ namespace asst { return m_copilot_cfg_unique_ins; } + RoguelikeCopilotConfiger& roguelike() noexcept + { + return m_roguelike_cfg_unique_ins; + } OcrPack& ocr() noexcept { return m_ocr_pack_unique_ins; @@ -101,6 +106,10 @@ namespace asst { return m_copilot_cfg_unique_ins; } + const RoguelikeCopilotConfiger& roguelike() const noexcept + { + return m_roguelike_cfg_unique_ins; + } const OcrPack& ocr() const noexcept { return m_ocr_pack_unique_ins; @@ -125,6 +134,7 @@ namespace asst RecruitConfiger m_recruit_cfg_unique_ins; CombatRecruitConfiger m_combatrecruit_cfg_unique_ins; CopilotConfiger m_copilot_cfg_unique_ins; + RoguelikeCopilotConfiger m_roguelike_cfg_unique_ins; ItemConfiger m_item_cfg_unique_ins; InfrastConfiger m_infrast_cfg_unique_ins; OcrPack m_ocr_pack_unique_ins; diff --git a/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp b/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp index 0167a779ff..d33fc8bf29 100644 --- a/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp +++ b/src/MeoAssistant/RoguelikeBattleTaskPlugin.cpp @@ -37,7 +37,7 @@ bool asst::RoguelikeBattleTaskPlugin::_run() { bool getted_info = get_stage_info(); - speed_up(); + //speed_up(); if (!getted_info) { return true; diff --git a/src/MeoAssistant/RoguelikeCopilotConfiger.cpp b/src/MeoAssistant/RoguelikeCopilotConfiger.cpp new file mode 100644 index 0000000000..e85a85a515 --- /dev/null +++ b/src/MeoAssistant/RoguelikeCopilotConfiger.cpp @@ -0,0 +1,161 @@ +#include "RoguelikeCopilotConfiger.h" + +#include + +#include "Logger.hpp" + +bool asst::RoguelikeCopilotConfiger::parse(const json::value& json) +{ + std::string stage_name = json.at("stage_name").as_string(); + + std::vector actions_vec; + + for (const auto& action_info : json.at("actions").as_array()) { + RoguelikeBattleAction action; + + static const std::unordered_map ActionTypeMapping = { + { "Deploy", BattleActionType::Deploy }, + { "DEPLOY", BattleActionType::Deploy }, + { "deploy", BattleActionType::Deploy }, + { "部署", BattleActionType::Deploy }, + + { "Skill", BattleActionType::UseSkill }, + { "SKILL", BattleActionType::UseSkill }, + { "skill", BattleActionType::UseSkill }, + { "技能", BattleActionType::UseSkill }, + + { "Retreat", BattleActionType::Retreat }, + { "RETREAT", BattleActionType::Retreat }, + { "retreat", BattleActionType::Retreat }, + { "撤退", BattleActionType::Retreat }, + + { "AllSkill", BattleActionType::UseAllSkill }, + { "allskill", BattleActionType::UseAllSkill }, + { "ALLSKILL", BattleActionType::UseAllSkill }, + { "Allskill", BattleActionType::UseAllSkill }, + { "全部技能", BattleActionType::UseAllSkill }, + }; + + std::string type_str = action_info.get("type", "Deploy"); + + if (auto iter = ActionTypeMapping.find(type_str); + iter != ActionTypeMapping.end()) { + action.type = iter->second; + } + else { + action.type = BattleActionType::Deploy; + } + action.kills = action_info.get("kills", 0); + + if (action_info.contains("roles")) { + for (const auto& role_info : action_info.at("roles").as_array()) { + static const std::unordered_map RoleMapping = { + { "MEDIC", BattleRole::Medic }, + { "Medic", BattleRole::Medic }, + { "medic", BattleRole::Medic }, + { "医疗", BattleRole::Medic }, + + { "WARRIOR", BattleRole::Warrior }, + { "Warrior", BattleRole::Warrior }, + { "warrior", BattleRole::Warrior }, + { "近卫", BattleRole::Warrior }, + + { "SPECIAL", BattleRole::Special }, + { "Special", BattleRole::Special }, + { "special", BattleRole::Special }, + { "特种", BattleRole::Special }, + + { "SNIPER", BattleRole::Sniper }, + { "Sniper", BattleRole::Sniper }, + { "sniper", BattleRole::Sniper }, + { "狙击", BattleRole::Sniper }, + + { "PIONEER", BattleRole::Pioneer }, + { "Pioneer", BattleRole::Pioneer }, + { "pioneer", BattleRole::Pioneer }, + { "先锋", BattleRole::Pioneer }, + + { "TANK", BattleRole::Tank }, + { "Tank", BattleRole::Tank }, + { "tank", BattleRole::Tank }, + { "重装", BattleRole::Tank }, + + { "SUPPORT", BattleRole::Support }, + { "Support", BattleRole::Support }, + { "support", BattleRole::Support }, + { "辅助", BattleRole::Support }, + + { "CASTER", BattleRole::Caster }, + { "Caster", BattleRole::Caster }, + { "caster", BattleRole::Caster }, + { "术师", BattleRole::Caster }, + + { "Drone", BattleRole::Drone }, + { "DRONE", BattleRole::Drone }, + { "drone", BattleRole::Drone }, + { "召唤物", BattleRole::Drone }, + { "无人机", BattleRole::Drone } + }; + + std::string role_name = role_info.as_string(); + if (auto iter = RoleMapping.find(role_name); + iter != RoleMapping.end()) { + action.roles.emplace_back(iter->second); + } + else { + Log.error("Unknown role", role_name); + } + } + } + + 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 }, + + { "Left", BattleDeployDirection::Left }, + { "LEFT", BattleDeployDirection::Left }, + { "left", BattleDeployDirection::Left }, + { "左", BattleDeployDirection::Left }, + + { "Up", BattleDeployDirection::Up }, + { "UP", BattleDeployDirection::Up }, + { "up", BattleDeployDirection::Up }, + { "上", BattleDeployDirection::Up }, + + { "Down", BattleDeployDirection::Down }, + { "DOWN", BattleDeployDirection::Down }, + { "down", BattleDeployDirection::Down }, + { "下", BattleDeployDirection::Down }, + + { "None", BattleDeployDirection::None }, + { "NONE", BattleDeployDirection::None }, + { "none", BattleDeployDirection::None }, + { "无", BattleDeployDirection::None }, + }; + + std::string direction_str = action_info.get("direction", "Right"); + + if (auto iter = DeployDirectionMapping.find(direction_str); + iter != DeployDirectionMapping.end()) { + action.direction = iter->second; + } + else { + action.direction = BattleDeployDirection::Right; + } + action.waiting_cost = action_info.get("waiting_cost", false); + action.pre_delay = action_info.get("pre_delay", 0); + action.rear_delay = action_info.get("rear_delay", 0); + action.time_out = action_info.get("timeout", INT_MAX); + + actions_vec.emplace_back(std::move(action)); + } + + m_battle_actions[std::move(stage_name)] = std::move(actions_vec); + + return true; +} diff --git a/src/MeoAssistant/RoguelikeCopilotConfiger.h b/src/MeoAssistant/RoguelikeCopilotConfiger.h new file mode 100644 index 0000000000..6b8ff714e2 --- /dev/null +++ b/src/MeoAssistant/RoguelikeCopilotConfiger.h @@ -0,0 +1,26 @@ +#pragma once +#include "AbstractConfiger.h" + +#include "AsstBattleDef.h" + +namespace asst +{ + class RoguelikeCopilotConfiger : public AbstractConfiger + { + public: + virtual ~RoguelikeCopilotConfiger() = default; + + bool contains_actions(const std::string& stage_name) const noexcept + { + return m_battle_actions.find(stage_name) != m_battle_actions.cend(); + } + + auto get_actions(const std::string& stage_name) const noexcept + { + return m_battle_actions.at(stage_name); + } + protected: + virtual bool parse(const json::value& json) override; + std::unordered_map> m_battle_actions; + }; +}